Come terminare il codice C ++


267

Vorrei che il mio codice C ++ smettesse di funzionare se si verifica una certa condizione, ma non sono sicuro di come farlo. Quindi, in qualsiasi momento, se ifun'istruzione è vera, termina il codice in questo modo:

if (x==1)
{
    kill code;
}

98
Usa la logica corretta. Nel main()ritorno di utilizzo, nelle funzioni utilizzare un valore di ritorno corretto o generare un'eccezione corretta. Non usare exit()!
kay - SE è malvagio

6
@JonathanLeffler: una volta compilato con NDEBUGdefinito, assertprobabilmente diventerà un no-op, quindi non dovresti fare affidamento su di esso per altro che rilevare i bug.
MvG,

13
@jamesqf: un returnda main()è perfettamente logico. Imposta il codice di uscita segnalato dal programma al sistema operativo, che può essere utile in molti casi (ad esempio se il programma può essere utilizzato come parte di un processo più ampio).
AAT,

11
È interessante notare che questo Q è un duplicato di questo di 5 anni fa, la cui risposta accettata è abbastanza diversa: stackoverflow.com/questions/1116493/how-to-quit-ac-program Immagino ci siano voluti 5 anni per far capire alla comunità che ciecamente chiamare std :: exit potrebbe essere male?
Brandin,

5
Domande Perché l'uso è exit()considerato negativo? - SO 25141737 e come uscire da un programma C ++? - SO 1116493 sono ora chiusi come duplicati di questo. Il primo di questi ha un riferimento al "software solo per crash" che potrebbe valere la pena dare un'occhiata, anche se solo per stimolare il tuo pensiero su come scrivere software robusto.
Jonathan Leffler,

Risposte:


431

Esistono diversi modi, ma prima devi capire perché la pulizia degli oggetti è importante, e quindi la ragione std::exitè emarginata tra i programmatori C ++.

RAII e Stack Unwinding

C ++ utilizza un linguaggio chiamato RAII , che in termini semplici significa che gli oggetti devono eseguire l'inizializzazione nel costruttore e la pulizia nel distruttore. Ad esempio, la std::ofstreamclasse [può] aprire il file durante il costruttore, quindi l'utente esegue operazioni di output su di esso e infine alla fine del suo ciclo di vita, solitamente determinato dal suo ambito, viene chiamato il distruttore che essenzialmente chiude il file e scarica qualsiasi contenuto scritto sul disco.

Cosa succede se non si arriva al distruttore per svuotare e chiudere il file? Chissà! Ma forse non scriverà tutti i dati che avrebbe dovuto scrivere nel file.

Ad esempio, considera questo codice

#include <fstream>
#include <exception>
#include <memory>

void inner_mad()
{
    throw std::exception();
}

void mad()
{
    auto ptr = std::make_unique<int>();
    inner_mad();
}

int main()
{
    std::ofstream os("file.txt");
    os << "Content!!!";

    int possibility = /* either 1, 2, 3 or 4 */;

    if(possibility == 1)
        return 0;
    else if(possibility == 2)
        throw std::exception();
    else if(possibility == 3)
        mad();
    else if(possibility == 4)
        exit(0);
}

Quello che succede in ogni possibilità è:

  • Possibilità 1: Return essenzialmente lascia l'ambito della funzione corrente, quindi è a conoscenza della fine del ciclo di vita di oschiamare quindi il suo distruttore e fare una pulizia adeguata chiudendo e scaricando il file sul disco.
  • Possibilità 2: il lancio di un'eccezione si occupa anche del ciclo di vita degli oggetti nell'ambito corrente, facendo così una pulizia adeguata ...
  • Possibilità 3: qui entra in azione lo svolgersi della pila! Anche se viene generata l'eccezione inner_mad, lo svolgitore eseguirà la pila di made mainper eseguire una corretta pulizia, tutti gli oggetti verranno distrutti correttamente, incluso ptre os.
  • Possibilità 4: bene, qui? exitè una funzione C e non è consapevole né compatibile con gli idiomi C ++. Essa non si esegue la pulizia sui vostri oggetti, tra cui osnello stesso ambito. Quindi il tuo file non verrà chiuso correttamente e per questo motivo il contenuto potrebbe non essere mai scritto al suo interno!
  • Altre possibilità: lascerà solo l'ambito principale, eseguendo un implicito return 0e quindi avendo lo stesso effetto della possibilità 1, ovvero una corretta pulizia.

Ma non essere così sicuro di quello che ti ho appena detto (principalmente possibilità 2 e 3); continua a leggere e scopriremo come eseguire una corretta pulizia basata sulle eccezioni.

Possibili modi per finire

Ritorna dalla principale!

Dovresti farlo quando possibile; preferisco sempre tornare dal tuo programma restituendo uno stato di uscita corretto da quello principale.

Il chiamante del tuo programma, e possibilmente il sistema operativo, potrebbe voler sapere se ciò che il tuo programma avrebbe dovuto fare è stato fatto con successo o no. Per questo stesso motivo è necessario restituire zero o EXIT_SUCCESSsegnalare che il programma è stato chiuso correttamente e EXIT_FAILUREper segnalare che il programma è stato terminato senza successo, qualsiasi altra forma di valore di ritorno è definita dall'implementazione ( §18.5 / 8 ).

Tuttavia potresti essere molto in profondità nello stack di chiamate e restituirlo tutto potrebbe essere doloroso ...

[Non] generare un'eccezione

Il lancio di un'eccezione eseguirà la corretta pulizia degli oggetti utilizzando lo svolgimento dello stack, chiamando il distruttore di ogni oggetto in qualsiasi ambito precedente.

Ma ecco il trucco ! Viene definito dall'implementazione se lo svolgimento dello stack viene eseguito quando un'eccezione generata non viene gestita (dalla clausola catch (...)) o anche se si ha una noexceptfunzione nel mezzo dello stack di chiamate. Questo è indicato nel § 15.5.1 [tranne il termine] :

  1. In alcune situazioni, la gestione delle eccezioni deve essere abbandonata per tecniche di gestione degli errori meno impercettibili. [Nota: queste situazioni sono:

    [...]

    - quando il meccanismo di gestione delle eccezioni non riesce a trovare un gestore per un'eccezione generata (15.3) o quando la ricerca di un gestore (15.3) incontra il blocco più esterno di una funzione con una noexceptspecifica che non consente l'eccezione (15.4), oppure [...]

    [...]

  2. In questi casi, viene chiamato std :: terminate () (18.8.3). Nella situazione in cui non viene trovato alcun gestore di corrispondenza, viene definito dall'implementazione se lo stack viene srotolato o meno prima che std :: terminate () venga chiamato [...]

Quindi dobbiamo prenderlo!

Lancia un'eccezione e prendila in linea di massima!

Poiché le eccezioni non rilevate potrebbero non svolgere lo svolgimento dello stack (e di conseguenza non eseguiranno una corretta pulizia) , dovremmo rilevare l'eccezione in main e quindi restituire uno stato di uscita ( EXIT_SUCCESSo EXIT_FAILURE).

Quindi un setup probabilmente buono sarebbe:

int main()
{
    /* ... */
    try
    {
        // Insert code that will return by throwing a exception.
    }
    catch(const std::exception&)  // Consider using a custom exception type for intentional
    {                             // throws. A good idea might be a `return_exception`.
        return EXIT_FAILURE;
    }
    /* ... */
}

[Non] std :: exit

Ciò non esegue alcun tipo di svolgimento dello stack e nessun oggetto vivo nello stack chiamerà il rispettivo distruttore per eseguire la pulizia.

Questo è applicato in §3.6.1 / 4 [basic.start.init] :

La chiusura del programma senza uscire dal blocco corrente (ad es. Chiamando la funzione std :: exit (int) (18.5)) non distrugge alcun oggetto con durata di memorizzazione automatica (12.4) . Se viene chiamato std :: exit per terminare un programma durante la distruzione di un oggetto con durata della memoria statica o thread, il programma ha un comportamento indefinito.

Pensaci ora, perché dovresti fare una cosa del genere? Quanti oggetti hai dolorosamente danneggiato?

Altre alternative [come cattive]

Esistono altri modi per terminare un programma (oltre all'arresto anomalo) , ma non sono consigliati. Per motivi di chiarezza verranno presentati qui. Notare come la normale chiusura del programma non significhi lo svolgersi dello stack ma uno stato corretto per il sistema operativo.

  • std::_Exit provoca una normale chiusura del programma, e il gioco è fatto.
  • std::quick_exitprovoca una normale chiusura del programma e chiama i std::at_quick_exitgestori, non viene eseguita alcuna altra pulizia.
  • std::exitprovoca una normale chiusura del programma e quindi chiama i std::atexitgestori. Vengono eseguiti altri tipi di pulizia come la chiamata di distruttori di oggetti statici.
  • std::abortprovoca una chiusura anomala del programma, non viene eseguita alcuna pulizia. Questo dovrebbe essere chiamato se il programma è terminato in un modo davvero inaspettato. Non farà altro che segnalare al sistema operativo la chiusura anomala. Alcuni sistemi eseguono un dump core in questo caso.
  • std::terminatechiama il std::terminate_handlerquale chiama std::abortper impostazione predefinita.

25
Questo è abbastanza informativo: in particolare non mi sono mai reso conto che lanciare un'eccezione che non viene gestita da nessuna parte (per esempio: alcuni newlanci std::bad_alloce il tuo programma si sono dimenticati di catturare quell'eccezione ovunque) non scioglierà correttamente lo stack prima di terminare. Questo sembra sciocco a me: sarebbe stato facile essenzialmente avvolgere la chiamata a mainun banale try{- }catch(...){}blocco che garantisca la pila di svolgimento sia correttamente fatto in questi casi, a costo zero (e con questo intendo: i programmi che non utilizzano questo non pagherà pena). C'è qualche motivo particolare per cui ciò non viene fatto?
Marc van Leeuwen,

12
@MarcvanLeeuwen un possibile motivo è il debug: si desidera entrare nel debugger non appena viene generata un'eccezione non gestita. Il riavvolgimento dello stack e la pulizia eliminerebbe il contesto di ciò che ha causato il crash di cui si desidera eseguire il debug. Se non è presente alcun debugger, potrebbe essere meglio eseguire il dump del core in modo da poter eseguire l'analisi post mortem.
Hugh Allen,

11
A volte il mio browser si blocca (incolpo Flash) e consuma diversi gigabyte di RAM e il mio sistema operativo scarica le pagine sul disco rigido rendendo tutto lento. Quando chiudo il browser, si svolge correttamente lo svolgimento dello stack, il che significa che tutti quei gigabyte di RAM vengono letti dal disco rigido e copiati nella memoria solo per essere rilasciati, il che richiede circa un minuto. Vorrei tanto che avessero usato std::abortinvece in modo che il sistema operativo potesse rilasciare tutta la memoria, i socket e i descrittori di file senza scambiare per un minuto.
nwp,

2
@nwp, capisco la sensazione. Ma uccidere all'istante può danneggiare i file, non salvare le mie schede più recenti, ecc :)
Paul Draper,

2
@PaulDraper Non lo considererei certamente tollerabile se un browser non fosse in grado di ripristinare le schede che avevo aperto prima di una perdita di potenza. Naturalmente se avessi appena aperto una scheda e non avessi ancora avuto il tempo di salvarla, sarebbe andata persa. Ma a parte questo, dico che non ci sono scuse per perderlo.
Kasperd,

61

Come ha detto Martin York, l'uscita non esegue la pulizia necessaria come fa il ritorno.

È sempre meglio usare il ritorno nel luogo di uscita. Nel caso in cui non ci si trovi in ​​main, ovunque si desideri uscire dal programma, tornare prima in main.

Considera l'esempio seguente. Con il seguente programma, verrà creato un file con il contenuto indicato. Ma se return è commentato e exit senza commento (0), il compilatore non ti assicura che il file avrà il testo richiesto.

int main()
{
    ofstream os("out.txt");
    os << "Hello, Can you see me!\n";
    return(0);
    //exit(0);
}

Non solo, Avere più punti di uscita in un programma renderà più difficile il debug. Utilizzare exit solo quando può essere giustificato.


2
Cosa mi consiglia di ottenere questo comportamento in un programma un po 'più grande? Come si ritorna sempre in modo pulito se da qualche parte più in profondità nel codice viene attivata una condizione di errore che dovrebbe uscire dal programma?
Janusz,

5
@Janusz, In tal caso, è possibile utilizzare / generare eccezioni, se non restituire un valore predefinito, ad esempio, avere un valore restituito dalla funzione, come ad esempio restituire 0 in caso di successo, 1 in caso di errore, ma continuare l'esecuzione , -1 in caso di errore e uscire dal programma. In base al valore restituito dalla funzione, se si tratta di un errore, è sufficiente tornare dal principale dopo aver eseguito altre attività di pulizia. Infine, usa l'uscita con giudizio, non intendo evitarlo.
Narendra N,

1
@NarendraN, la "pulizia necessaria" è vaga: il sistema operativo si occuperà (Windows / Linux) che gli handle di memoria e di file vengano rilasciati correttamente. Per quanto riguarda l'output del file "mancante": se si insiste sul fatto che questo potrebbe essere un problema reale, consultare stackoverflow.com/questions/14105650/how-does-stdflush-work Se si dispone di una condizione di errore, la registrazione corretta indica che il programma ha raggiunto uno stato indefinito ed è possibile impostare un punto di interruzione proprio prima del punto di registrazione. In che modo questo rende più difficile il debug?
Markus,

2
Si noti che questa risposta è stata unita dalla domanda Come uscire da un programma C ++? - SO 1116493 . Questa risposta è stata scritta circa 6 anni prima che questa domanda fosse posta.
Jonathan Leffler,

per il moderno uso del C ++ return (EXIT_SUCCESS);invecereturn(0)
Jonas Stein,

39

Chiama la std::exitfunzione.   


2
Quali distruttori di oggetti vengono chiamati quando chiamate quella funzione?
Rob Kennedy,

37
exit () non ritorna. Quindi non può avvenire lo svolgimento della pila. Nemmeno gli oggetti globali non vengono distrutti. Ma verranno chiamate le funzioni registrate con atexit ().
Martin York,

16
Si prega di non chiamare exit()quando il codice della libreria è in esecuzione all'interno del mio processo host - quest'ultimo uscirà nel bel mezzo del nulla.
sharptooth,

5
Si noti che questa risposta è stata unita dalla domanda Come uscire da un programma C ++? - SO 1116493 . Questa risposta è stata scritta circa 6 anni prima che questa domanda fosse posta.
Jonathan Leffler,

23

La gente sta dicendo "call exit (codice di ritorno)", ma questa è una cattiva forma. In piccoli programmi va bene, ma ci sono una serie di problemi con questo:

  1. Alla fine avrai più punti di uscita dal programma
  2. Rende il codice più contorto (come usare goto)
  3. Non può rilasciare memoria allocata in fase di esecuzione

Davvero, l'unica volta che dovresti uscire dal problema è con questa linea in main.cpp:

return 0;

Se si utilizza exit () per gestire gli errori, è necessario conoscere le eccezioni (e le eccezioni di nidificazione), come metodo molto più elegante e sicuro.


9
In un ambiente multi-thread, un'eccezione generata in un thread diverso non verrà gestita tramite main (): alcune comunicazioni manuali tra thread sono necessarie prima della scadenza del thread subordinato.
Steve Gilham,

3
1. e 2. sono a carico del programmatore, con una registrazione adeguata questo non è un problema poiché l'espirazione si ferma per sempre. Per quanto riguarda 3: Questo è semplicemente sbagliato, il sistema operativo libererà la memoria, forse escludendo i dispositivi integrati / in tempo reale, ma se lo stai facendo, probabilmente conosci le tue cose.
Markus,

1
Si noti che questa risposta è stata unita dalla domanda Come uscire da un programma C ++? - SO 1116493 . Questa risposta è stata scritta circa 6 anni prima che questa domanda fosse posta.
Jonathan Leffler,

1
Se si è verificato un errore, non dovresti restituire 0. Dovresti restituire 1 (o forse un altro valore, ma 1 è sempre una scelta sicura).
Kef Schecter,

14

return 0;mettilo dove vuoi int main()e il programma si chiuderà immediatamente.


the-nightman, @ evan-carslake, e tutti gli altri, aggiungerei anche che questa domanda SO # 36707 dà una discussione sul fatto che una dichiarazione di ritorno debba avvenire ovunque in una routine. Questa è una soluzione; tuttavia, a seconda della situazione specifica, non direi che questa è la soluzione migliore.
localhost,

1
OP non ha detto nulla al riguardo, quindi penso che sia piuttosto avventato supporre che il codice fosse pensato per essere all'interno della funzione main.
Marc van Leeuwen,

@localhost Una dichiarazione di ritorno è completamente facoltativa in ogni situazione, in qualsiasi condizione. Tuttavia, int main()è diverso. Il programma utilizza int main()per iniziare e finire. Non c'è altro modo per farlo. Il programma verrà eseguito finché non si restituisce true o false. Quasi sempre restituirai 1 o true, per indicare che il programma è stato chiuso correttamente (es: potrebbe usare false se non riuscissi a liberare memoria o per qualsivoglia motivo.) Lasciar terminare l'esecuzione del programma stesso è sempre una cattiva idea, esempio:int main() { int x = 2; int foo = x*5; std::cout << "blah"; }
Evan Carslake,

@EvanCarslake Capito, se hai notato un commento che ho postato altrove per questa domanda, ne ho familiarità int main; tuttavia, non è sempre una buona idea avere più dichiarazioni di ritorno in una routine principale. Un potenziale avvertimento è migliorare la leggibilità del codice; tuttavia, utilizzare qualcosa come un flag booleano che cambia stato per impedire l'esecuzione di determinate sezioni di codice in quello stato dell'applicazione. Personalmente, trovo che una dichiarazione di reso comune renda la maggior parte delle applicazioni più leggibili. Infine, domande sulla pulizia della memoria e sulla chiusura corretta degli oggetti I / O prima dell'uscita.
localhost,

2
@EvanCarslake non tornare trueda mainindicare una terminazione corretta. È necessario restituire zero o EXIT_SUCCESS(o, se lo si desidera false, che verrà convertito implicitamente in zero) per indicare la terminazione normale. Per indicare un errore è possibile tornare EXIT_FAILURE. Qualsiasi altro significato di codice è definito dall'implementazione (sui sistemi POSIX significherebbe un vero codice di errore).
Ruslan,

11

Il programma termina quando il flusso di esecuzione raggiunge la fine della funzione principale.

Per terminarlo prima di allora, puoi usare la funzione exit (int status), dove status è un valore restituito a qualunque cosa abbia avviato il programma. 0 indica normalmente uno stato di non errore


2
Si noti che questa risposta è stata unita dalla domanda Come uscire da un programma C ++? - SO 1116493 . Questa risposta è stata scritta circa 6 anni prima che questa domanda fosse posta.
Jonathan Leffler,

11

Restituisci un valore dal tuo maino usa la exitfunzione. Entrambi prendono un int. Non importa quale valore restituisci a meno che tu non abbia un processo esterno in cerca del valore restituito.


3
Si noti che questa risposta è stata unita dalla domanda Come uscire da un programma C ++? - SO 1116493 . Questa risposta è stata scritta circa 6 anni prima che questa domanda fosse posta.
Jonathan Leffler,

11

Se si verifica un errore da qualche parte nel codice, generare un'eccezione o impostare il codice di errore. È sempre meglio generare un'eccezione anziché impostare i codici di errore.


2
Si noti che questa risposta è stata unita dalla domanda Come uscire da un programma C ++? - SO 1116493 . Questa risposta è stata scritta circa 6 anni prima che questa domanda fosse posta.
Jonathan Leffler,

9

Generalmente si utilizza il exit()metodo con uno stato di uscita appropriato .

Zero significherebbe una corsa di successo. Uno stato diverso da zero indica che si è verificato un tipo di problema. Questo codice di uscita viene utilizzato dai processi parent (ad esempio script di shell) per determinare se un processo è stato eseguito correttamente.


1
usando exit PUOI significare che hai un problema di progettazione. Se un programma si comporta correttamente, dovrebbe terminare quando principale return 0;. Presumo exit()sia simile assert(false);e quindi dovrebbe essere utilizzato solo nello sviluppo per problemi di cattura precoci.
CoffeDeveloper,

2
Si noti che questa risposta è stata unita dalla domanda Come uscire da un programma C ++? - SO 1116493 . Questa risposta è stata scritta circa 6 anni prima che questa domanda fosse posta.
Jonathan Leffler,

7

Oltre a chiamare exit (error_code) - che chiama i gestori atexit, ma non i distruttori RAII, ecc. - utilizzo sempre più eccezioni.

Sempre più il mio programma principale sembra

int main(int argc, char** argv) 
{
    try {
        exit( secondary_main(argc, argv );
    }
    catch(...) {
        // optionally, print something like "unexpected or unknown exception caught by main"
        exit(1);
    }
}

dove primary_main in cui vengono messe tutte le cose che erano originariamente - cioè il main originale è stato rinominato secondario_main, e viene aggiunto lo stub principale sopra. Questa è solo una cosa, in modo che non ci sia troppo codice tra il vassoio e catturare in main.

Se vuoi, prendi altri tipi di eccezione.
Mi piace abbastanza catturare i tipi di errore di stringa, come std :: string o char *, e stampare quelli nel gestore catch in linea di principio.

L'uso di eccezioni come questa consente almeno di chiamare i distruttori RAII, in modo che possano eseguire la pulizia. Che può essere piacevole e utile.

Nel complesso, la gestione degli errori C - uscita e segnali - e la gestione degli errori C ++ - tentare / catturare / lanciare eccezioni - giocano insieme in modo incoerente al meglio.

Quindi, dove si rileva un errore

throw "error message"

o qualche tipo di eccezione più specifico.


A proposito: sono ben consapevole che l'utilizzo di un tipo di dati come stringa, che può comportare l'allocazione dinamica della memoria, non è una buona idea all'interno o attorno a un gestore di eccezioni per le eccezioni che potrebbero essere correlate all'esaurimento della memoria. Le costanti di stringa in stile C non sono un problema.
Krazy Glew il

1
Non ha senso chiamare il exittuo programma. Dal momento che ci sei main, puoi solo return exitCode;.
Ruslan,

-1

Se l'istruzione if è in Loop È possibile utilizzare

 break; 

Se vuoi fuggire un po 'di codice e continuare ad eseguire il ciclo usa:

Continua;

Se l'istruzione if non è in Loop È possibile utilizzare:

 return 0;

Or 




  exit();

-2

La exit()funzione amico ... è definita in stdlib.h

Quindi è necessario aggiungere un preprocessore.

Inserisci include stdlib.hnella sezione dell'intestazione

Quindi usa exit();dove preferisci, ma ricorda di inserire un numero intero tra parentesi di uscita.

per esempio:

exit(0);

-2

Se la condizione per cui sto testando è davvero una brutta notizia, faccio questo:

*(int*) NULL= 0;

Questo mi dà un bel coredump da cui posso esaminare la situazione.


4
Questo potrebbe essere ottimizzato, poiché causa un comportamento indefinito. In effetti, l'intero compilatore con una simile dichiarazione può essere cancellato dal compilatore.
Ruslan,

-2

Per interrompere una condizione utilizzare il ritorno (0);

Quindi, nel tuo caso sarebbe:

    if(x==1)
    {
        return 0;
    }
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.