istruzione return vs exit () in main ()


197

Dovrei usare exit()o solo returndichiarazioni in main()? Personalmente preferisco le returnaffermazioni perché sento che è come leggere qualsiasi altra funzione e il controllo del flusso quando sto leggendo il codice è fluido (secondo me). E anche se voglio refactoring la main()funzione, avere returnsembra una scelta migliore di exit().

Fa exit()qualcosa di speciale che returnnon lo fa?

Risposte:


277

In realtà, non v'è una differenza, ma è sottile. Ha più implicazioni per C ++, ma le differenze sono importanti.

Quando chiamo returnin main(), distruttori saranno chiamati per i miei oggetti con ambito a livello locale. Se chiamo exit(), nessun distruttore verrà chiamato per i miei oggetti con ambito locale! Rileggilo. exit() non ritorna . Ciò significa che una volta che lo chiamo, non ci sono "backsies". Gli oggetti che hai creato in quella funzione non verranno distrutti. Spesso questo non ha implicazioni, ma a volte lo fa, come chiudere i file (sicuramente vuoi che tutti i tuoi dati vengano scaricati su disco?).

Si noti che gli staticoggetti verranno ripuliti anche se si chiama exit(). Infine, nota che, se lo usi abort(), nessun oggetto verrà distrutto. Cioè, nessun oggetto globale, nessun oggetto statico e nessun oggetto locale avranno i loro distruttori chiamati.

Procedere con cautela nel favorire l'uscita rispetto al ritorno.

http://groups.google.com/group/gnu.gcc.help/msg/8348c50030cfd15a


1
abort () esce con condizione di errore (codice di uscita diverso da zero) e può anche essere un core. Se è necessario uscire senza chiamare distruttori statici, utilizzare _exit.

7
@ Mike: c'è una differenza tra i buffer di file della libreria C e gli oggetti del flusso di file C ++. exit () - facente parte della libreria C - è stato progettato per coordinarsi e svuotare il primo, ma può bypassare il secondo: anche il contenuto standard del flusso C ++ non viene scaricato sul disco (provalo - l'ho fatto, non funziona w / Linux / GCC) e ovviamente i tipi definiti dall'utente che hanno bufferizzato l'I / O non possono essere scaricati.
Tony Delroy,

9
Nota: l'affermazione: nessun distruttore verrà chiamato per i miei oggetti con ambito locale! non è più vero per C ++ 11: - Gli oggetti associati al thread corrente con durata della memorizzazione del thread vengono distrutti (solo C ++ 11). cplusplus.com/reference/cstdlib/exit
Ilendir l'

7
Significa thread_localche verranno chiamati i distruttori degli oggetti. I distruttori per altri oggetti locali non vengono ancora chiamati. ideone.com/Y6Dh3f
HolyBlackCat

3
A proposito, e solo per essere pedanti e perché questa risposta può ancora essere fonte di confusione per i lettori che usano C: Per C il problema della exit()chiusura di file in modo pulito è in realtà sbagliato. L'unica volta in cui i dati potrebbero non essere cancellati è nel caso opposto: cioè se uno usa returnda main()e uno ha chiamato setbuf()o setvbuf()con un buffer dichiarato come memoria automatica in main()(come discusso nella risposta di R. di seguito). È davvero un peccato che questa domanda sia taggata con C e C ++ (e in stile codice - non è un problema di stile!).
Greg A. Woods,

25

Un'altra differenza: exitè una funzione di libreria standard, quindi è necessario includere intestazioni e collegamento con la libreria standard. Per illustrare (in C ++), questo è un programma valido:

int main() { return 0; }

ma per usare exitavrai bisogno di includere:

#include <stdlib.h>
int main() { exit(EXIT_SUCCESS); }

Inoltre questo aggiunge un ulteriore presupposto: che chiamare exitda mainha gli stessi effetti collaterali di restituire zero. Come altri hanno sottolineato, questo dipende dal tipo di eseguibile che stai costruendo (ad esempio, chi sta chiamando main). Stai codificando un'app che utilizza C-runtime? Un plugin Maya? Un servizio Windows? Un autista? Ogni caso richiederà una ricerca per vedere se exitè equivalente a return. L'uso di IMHO exitquando intendi davvero return rende il codice più confuso. OTOH, se davvero intendi exit , allora usalo sicuramente.


16

Esiste almeno un motivo per preferire exit: se uno dei tuoi atexitgestori fa riferimento a dati di durata della memorizzazione automatica in main, o se hai utilizzato setvbufo setbufper assegnare a uno dei flussi standard un buffer di durata della memorizzazione automatica main, quindi il ritorno da mainproduce comportamento indefinito, ma la chiamata exitè valida.

Un altro potenziale utilizzo (di solito riservato ai programmi giocattolo, tuttavia) è quello di uscire da un programma con invocazioni ricorsive di main.


1
@Seb non c'è niente di speciale main()- è solo una funzione come le altre. D'altra parte, poiché ha una menzione speciale nello standard, lo standard deve essere abbastanza attento a come definisce main()e alle cose vicine e care. Tuttavia, alla fine, sebbene lo standard non richieda (e non debba ) ai compilatori di fare qualcosa di speciale sull'archiviazione automatica main(). Si prega di leggere la nota 11 al di sotto del paragrafo a cui si fa riferimento nel commento.
Greg A. Woods,

1
@ GregA.Woods Interessante. Sembra che ci sia del testo normativo in contraddizione con un testo informativo. Secondo le direttive ISO / IEC , il riferimento normativo è considerato "indispensabile", laddove l'informativa è considerata solo supplementare ... Inoltre, l'uso della parola "volontà" per indicare un requisito non è valido; secondo il documento di cui sopra (allegato H). In sintesi, il testo informativo è sicuramente non valido.
Autistico

2
@Seb: l'intento non è ovviamente quello di ignorare i requisiti sul comportamento della memorizzazione automatica e la nota a piè di pagina è stata ovviamente scritta per chiarire questo. Sì, c'è una formulazione imprecisa e scadente nello standard C. Chiunque l'abbia letto lo sa. Sappiamo anche che il comitato generalmente non risolve problemi come questo perché l'intento è già evidente.
R .. GitHub smette di aiutare ICE il

1
@Seb: Questo non è un dibattito o un caso giudiziario per dimostrare che hai ragione. L'obiettivo dovrebbe essere quello di ottenere una chiara comprensione di ciò che è il linguaggio C attuale (come previsto e implementato) ed esprimerlo in risposte utili ai lettori. Il testo normativo è sottilmente sbagliato (contrariamente all'intento di ciò che avrebbe dovuto esprimere) in un modo che è essenzialmente fissato dalla nota a piè di pagina. Se non sei soddisfatto, invia un rapporto sui difetti, ma non aspettarti una risposta. Ecco come rotola WG14 ...
R .. GitHub

3
@Seb: Sembri credere che il linguaggio C possa essere compreso interpretando il testo in linguaggio naturale dello standard come se fosse completamente rigoroso. Questo semplicemente non è possibile. La specifica contiene errori e WG14 non perde tempo a riscrivere le cose quando una semplice nota a piè di pagina chiarisce che già sanno di aver fatto un errore, ma che il lettore può capirlo.
R .. GitHub smette di aiutare ICE l'

5

Uso sempre returnperché il prototipo standard per main()dice che restituisce un int.

Detto questo, alcune versioni degli standard offrono mainun trattamento speciale e presumono che restituisca 0 se non vi sono returndichiarazioni esplicite . Dato il seguente codice:

int foo() {}
int main(int argc, char *argv[]) {}

G ++ genera solo un avviso per foo()e ignora il ritorno mancante da main:

% g++ -Wall -c foo.cc
foo.cc: In function int foo()’:
foo.cc:1: warning: control reaches end of non-void function

Non conosco C, ma lo standard C ++ specifica che se non si restituisce un valore in main, si presume che restituisca 0.
Jason Baker,

Sembra che C99 sia lo stesso: faq.cprogramming.com/cgi-bin/…
Jason Baker,

2
C99 e C ++ restituiscono 0 se non è presente alcuna istruzione return, C90 no.
sabato

Solo perché una funzione viene dichiarata con un valore restituito non significa che è necessario utilizzare returnper terminarne l'esecuzione. La chiamata exit()è anche un modo valido e talvolta necessario per terminare l'esecuzione di qualsiasi funzione. In effetti, come io e altri abbiamo descritto altrove, chiamare exit()anche da main()trasmette un'intenzione molto più chiara di uscire da tutto il processo, preservare l'archiviazione automatica fino all'uscita dal processo e facilitare la manutenzione durante il futuro refactoring del codice. Per C usare returnin main()quando l'intenzione è di terminare il processo è quindi probabilmente una cattiva pratica.
Greg A. Woods,

@ GregA.Woods questa è la tua opinione, ma difficilmente merita un voto negativo! Ciò che ho scritto sopra è completamente coerente con lo standard , mentre la tua argomentazione è solo semantica.
Alnitak,

5

I FORTEMENTE secondo il commento di R. sull'utilizzo uscita () in modo da non dover memorizzazione automatica nella main()bonificata prima effettivamente il programma termina. Una return X;dichiarazione main()non è esattamente equivalente a una chiamata a exit(X);, dal momento che l'archiviazione dinamica dei main()svanisce quando main()ritorni, ma non svanire se una chiamata a exit()è fatto, invece.

Inoltre, in C o in qualsiasi linguaggio simile a C returnun'affermazione suggerisce fortemente al lettore che l'esecuzione continuerà nella funzione chiamante, e mentre questa continuazione dell'esecuzione è di solito tecnicamente vera se si considera la routine di avvio C che ha chiamato la propria main()funzione, non è esattamente che cosa si intende quando si intende terminare il processo.

Dopotutto, se vuoi terminare il tuo programma da qualsiasi altra funzione tranne main()che devi chiamare exit(). Fare altrettanto in modo coerente main()rende il tuo codice molto più leggibile, e rende anche molto più facile per chiunque ricodificare il tuo codice; cioè il codice copiato da main()un'altra funzione non si comporterà a causa di returndichiarazioni accidentali che avrebbero dovuto essere exit()chiamate.

Quindi, combinando tutti questi punti insieme la conclusione è che è una cattiva abitudine , almeno per C, usare areturn un'istruzione per terminare il programma main().


Si potrebbe trovare 5.1.2.2.3p1 dello standard C interessante ...
autistico

Questa risposta merita un'attenta considerazione per i programmi C, come contestualmente indicato nella risposta. Per l'uso con C ++, deve essere attentamente valutato con tutte le avvertenze precedentemente menzionate. Per C ++, suggerirei di evitare exit()in generale, ma lo uso se a throwo le abort()alternative non funzionano in un contesto specifico. Ma soprattutto evita exit()in main e usa invece il return in main come pratica tipica.
Eljay,

5

Exit () fa qualcosa di speciale che "return" non fa?

Con alcuni compilatori per piattaforme non comuni, exit()potrebbe tradurre il suo argomento nel valore di uscita del programma durante un ritorno damain() potrebbe semplicemente passare il valore direttamente all'ambiente host senza alcuna traduzione.

Lo standard richiede un comportamento identico in questi casi (in particolare, dice che restituire qualcosa che è intcompatibile main()dovrebbe essere equivalente a chiamare exit()con quel valore). Il problema è che diversi sistemi operativi hanno convenzioni diverse per l'interpretazione dei valori di uscita. Su molti sistemi (MOLTI!), 0 significa successo e qualsiasi altra cosa è un fallimento. Ma su, diciamo, VMS, i valori dispari significano successo e anche quelli significano fallimento. Se restituissi 0 da main(), un utente VMS vedrebbe un brutto messaggio su una violazione di accesso. In realtà non c'era una violazione di accesso - quello era semplicemente il messaggio standard associato al codice di errore 0.

Quindi l'ANSI è arrivato e benedetto EXIT_SUCCESSe EXIT_FAILUREcome argomenti a cui potresti passare exit(). Lo standard dice anche che exit(0)bisogna comportarsi in modo identico a exit(EXIT_SUCCESS), quindi la maggior parte delle implementazioni definiscono EXIT_SUCCESSa 0.

Lo standard, quindi, ti mette in relazione con VMS, in quanto non lascia alcun modo standard per restituire un codice di errore che ha il valore 0.

Il compilatore VAX / VMS C dell'inizio degli anni '90 non interpretava quindi il valore di ritorno main(), ma semplicemente restituiva qualsiasi valore all'ambiente host. Ma se lo utilizzassi exit(), farebbe ciò che lo standard richiesto: tradurre EXIT_SUCCESS(o 0) in un codice di successo e EXIT_FAILUREin un codice di errore generico. Per utilizzarlo EXIT_SUCCESS, è stato necessario passarlo a exit(), da cui non è stato possibile restituirlo main(). Non so se versioni più moderne di quel compilatore conservassero quel comportamento.

Un programma C portatile usato per assomigliare a questo:

#include <stdio.h>
#include <stdlib.h>

int main() {
  printf("Hello, World!\n");
  exit(EXIT_SUCCESS);  /* to get good return value to OS */
  /*NOTREACHED*/ /* to silence lint warning */
  return 0;  /* to silence compiler warning */
}

A parte: se ricordo bene, la convenzione VMS per i valori di uscita è più sfumata di pari / dispari. In realtà usa qualcosa come i tre bit bassi per codificare un livello di gravità. In generale, tuttavia, i livelli di gravità dispari indicavano il successo o informazioni varie e quelli pari indicavano errori.


Alcuni vecchi compilatori pre-ANSI potrebbero aver trattato il valore returnedin modo maindiverso dal valore passato aexit - ma lo standard dice specificamente: "Se il tipo restituito della mainfunzione è compatibile con un tipo int, un ritorno dalla chiamata iniziale alla mainfunzione è equivale a chiamare la exitfunzione con il valore restituito dalla mainfunzione come argomento ". Questo è C11; C89 / C90 aveva quasi la stessa formulazione.
Keith Thompson,

Infatti. Tuttavia, alcuni compilatori dell'era ANSI non hanno ottenuto questo diritto e hanno richiesto l'uso esplicito di exit per ottenere il valore di ritorno corretto restituito all'ambiente host. Poiché lo standard (anche allora) richiede che 0 sia trattato allo stesso modo EXIT_SUCCESS, non c'era modo di restituire uno stato di errore specifico della piattaforma con il valore 0, il che potrebbe essere il motivo per cui alcuni compilatori dell'era hanno trattato il ritorno da principale e exit()diversamente.
Adrian McCarthy,

Hai una citazione per questo? Un problema separato è se gli eventuali compilatori attuali hanno quel particolare bug. La tua risposta è formulata nel tempo presente.
Keith Thompson,

Questa è una critica giusta. Ho modificato il testo per limitare l'ambito al caso specifico che conosco.
Adrian McCarthy,

0

In C il ritorno da mainè esattamente lo stesso di una chiamata exitcon lo stesso valore.

La sezione 5.1.2.2.3 della norma C afferma:

Se il tipo restituito della funzione principale è un tipo compatibile con int, un ritorno dalla chiamata iniziale alla funzione principale equivale a chiamare la funzione di uscita con il valore restituito dalla funzione principale come argomento ; 11) il raggiungimento di} che termina la funzione principale restituisce un valore di 0. Se il tipo restituito non è compatibile con int, lo stato di terminazione restituito all'ambiente host non è specificato.

Le regole per C ++ sono leggermente diverse come indicato in altre risposte.


-1

In realtà c'è una differenza tra exit(0)e return(0)inmain - quando la tua mainfunzione viene chiamata più volte.

Il seguente programma

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char** argv) {
  if (argc == 0)
    return(0);
  printf("%d", main(argc - 1, argv));
}

Correre come

./program 0 0 0 0

Si tradurrà nel seguente output:

00000

Tuttavia questo:

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char** argv) {
  if (argc == 0)
    exit(0);
  printf("%d", main(argc - 1, argv));
}

Non stampa nulla indipendentemente dagli argomenti.

Se sei sicuro che nessuno ti chiamerà mainesplicitamente, non è tecnicamente una grande differenza in generale, ma mantenere un codice più chiaro exitsarebbe molto meglio. Se per qualche motivo vuoi chiamaremain , dovresti adattarlo alle tue esigenze.

Parlando di C.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.