Comment terminer le code C ++


267

Je voudrais que mon code C ++ cesse de fonctionner si une certaine condition est remplie, mais je ne sais pas comment faire cela. Donc, à tout moment si une ifdéclaration est vraie, terminez le code comme ceci:

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

98
Utilisez une logique appropriée. Dans main()use return, dans les fonctions, utilisez une valeur de retour appropriée ou lâchez une exception appropriée. Ne pas utiliser exit()!
kay - SE is evil

6
@JonathanLeffler: Une fois compilé avec NDEBUGdéfini, assertdeviendra probablement un no-op, donc vous ne devriez pas vous en remettre à plus que de détecter des bogues.
MvG

13
@jamesqf: un returnfrom main()est parfaitement logique. Il définit le code de sortie signalé par le programme au système d'exploitation, ce qui peut être utile dans de nombreux cas (si votre programme peut être utilisé dans le cadre d'un processus plus vaste, par exemple).
AAT

11
Fait intéressant, ce Q est un doublon de celui d'il y a 5 ans, dont la réponse acceptée est assez différente: stackoverflow.com/questions/1116493/how-to-quit-ac-program Je suppose qu'il a fallu 5 ans à la communauté pour l'apprendre aveuglément. appeler std :: exit peut être mauvais?
Brandin

5
Questions Pourquoi l'utilisation est-elle exit()considérée comme mauvaise? - SO 25141737 et Comment quitter un programme C ++? - SO 1116493 sont désormais fermés en tant que doublons de celui-ci. Le premier de ceux-ci fait référence au `` logiciel crash uniquement '' qui pourrait valoir le coup d'œil, ne serait-ce que pour stimuler votre réflexion sur la façon d'écrire un logiciel robuste.
Jonathan Leffler

Réponses:


431

Il existe plusieurs façons, mais vous devez d'abord comprendre pourquoi le nettoyage des objets est important, et donc la raison std::exitest marginalisée parmi les programmeurs C ++.

RAII et déroulement de la pile

C ++ utilise un idiome appelé RAII , ce qui signifie en termes simples que les objets doivent effectuer l'initialisation dans le constructeur et le nettoyage dans le destructeur. Par exemple, la std::ofstreamclasse [peut] ouvrir le fichier pendant le constructeur, puis l'utilisateur effectue des opérations de sortie sur celui-ci, et enfin à la fin de son cycle de vie, généralement déterminé par sa portée, le destructeur est appelé qui ferme essentiellement le fichier et vide tout contenu écrit sur le disque.

Que se passe-t-il si vous n'obtenez pas le destructeur pour vider et fermer le fichier? Qui sait! Mais il est possible qu'il n'écrive pas toutes les données qu'il était censé écrire dans le fichier.

Par exemple, considérez ce code

#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);
}

Ce qui se passe dans chaque possibilité est:

  • Possibilité 1: Return laisse essentiellement la portée de la fonction actuelle, donc il connaît la fin du cycle de vie d' osappeler ainsi son destructeur et de faire un nettoyage approprié en fermant et en vidant le fichier sur le disque.
  • Possibilité 2: le fait de lever une exception prend également en charge le cycle de vie des objets dans la portée actuelle, effectuant ainsi un nettoyage approprié ...
  • Possibilité 3: Ici, le déroulement de la pile entre en action! Même si l'exception est lancée inner_mad, le dérouleur passera par la pile de madet mainpour effectuer un nettoyage approprié, tous les objets seront détruits correctement, y compris ptret os.
  • Possibilité 4: Eh bien, ici? exitest une fonction C et elle n'est pas consciente ni compatible avec les idiomes C ++. Il n'effectue pas de nettoyage sur vos objets, y compris osdans la même portée. Votre fichier ne sera donc pas fermé correctement et pour cette raison, le contenu pourrait ne jamais y être écrit!
  • Autres possibilités: il ne restera plus que la portée principale, en effectuant un implicite return 0et en ayant ainsi le même effet que la possibilité 1, c'est-à-dire un nettoyage correct.

Mais ne soyez pas si sûr de ce que je viens de vous dire (principalement les possibilités 2 et 3); continuez à lire et nous découvrirons comment effectuer un nettoyage approprié basé sur les exceptions.

Façons possibles de mettre fin

Retour de main!

Vous devriez le faire chaque fois que possible; préférez toujours revenir de votre programme en renvoyant un état de sortie correct de main.

L'appelant de votre programme, et peut-être le système d'exploitation, voudra peut-être savoir si ce que votre programme était censé faire a été fait avec succès ou non. Pour cette même raison, vous devez retourner zéro ou EXIT_SUCCESSsignaler que le programme s'est terminé avec succès et EXIT_FAILUREsignaler que le programme s'est terminé sans succès, toute autre forme de valeur de retour est définie par l'implémentation ( §18.5 / 8 ).

Cependant, vous pouvez être très profondément dans la pile des appels, et retourner tout cela peut être douloureux ...

[Ne pas] lever une exception

Le lancement d'une exception effectuera un nettoyage d'objet approprié à l'aide du déroulement de la pile, en appelant le destructeur de chaque objet dans n'importe quelle portée précédente.

Mais voici le hic ! Il est défini par l'implémentation si le déroulement de la pile est effectué lorsqu'une exception levée n'est pas gérée (par la clause catch (...)) ou même si vous avez une noexceptfonction au milieu de la pile des appels. Ceci est indiqué au §15.5.1 [sauf.terminate] :

  1. Dans certaines situations, la gestion des exceptions doit être abandonnée pour des techniques de gestion des erreurs moins subtiles. [Remarque: ces situations sont les suivantes:

    [...]

    - lorsque le mécanisme de gestion des exceptions ne peut pas trouver de gestionnaire pour une exception levée (15.3), ou lorsque la recherche d'un gestionnaire (15.3) rencontre le bloc le plus à l'extérieur d'une fonction avec une noexceptspécification qui n'autorise pas l'exception (15.4), ou [...]

    [...]

  2. Dans de tels cas, std :: terminate () est appelé (18.8.3). Dans le cas où aucun gestionnaire correspondant n'est trouvé, il est défini par l'implémentation si la pile est déroulée ou non avant que std :: terminate () soit appelé [...]

Nous devons donc l'attraper!

Jetez une exception et attrapez-la au principal!

Étant donné que les exceptions non capturées peuvent ne pas effectuer le déroulement de la pile (et, par conséquent, ne pas effectuer le nettoyage approprié) , nous devons intercepter l'exception en principal, puis renvoyer un état de sortie ( EXIT_SUCCESSou EXIT_FAILURE).

Donc, une bonne configuration pourrait être:

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;
    }
    /* ... */
}

[Ne pas] std :: exit

Cela n'effectue aucune sorte de déroulement de la pile, et aucun objet vivant sur la pile n'appellera son destructeur respectif pour effectuer le nettoyage.

Ceci est appliqué au §3.6.1 / 4 [basic.start.init] :

Terminer le programme sans quitter le bloc courant (par exemple, en appelant la fonction std :: exit (int) (18.5)) ne détruit aucun objet avec une durée de stockage automatique (12.4) . Si std :: exit est appelé pour terminer un programme pendant la destruction d'un objet avec une durée de stockage statique ou de thread, le programme a un comportement indéfini.

Pensez-y maintenant, pourquoi feriez-vous une telle chose? Combien d'objets avez-vous douloureusement endommagés?

Autres alternatives [aussi mauvaises]

Il existe d'autres façons de mettre fin à un programme (autre que le plantage) , mais elles ne sont pas recommandées. Pour plus de précision, ils vont être présentés ici. Remarquez comment la fin normale d'un programme ne signifie pas un déroulement de la pile mais un état correct pour le système d'exploitation.

  • std::_Exit provoque une interruption normale du programme, et c'est tout.
  • std::quick_exitprovoque un arrêt normal du programme et appelle les std::at_quick_exitgestionnaires, aucun autre nettoyage n'est effectué.
  • std::exitprovoque un arrêt normal du programme et appelle ensuite les std::atexitgestionnaires. D'autres types de nettoyage sont effectués, comme l'appel de destructeurs d'objets statiques.
  • std::abortprovoque une interruption anormale du programme, aucun nettoyage n'est effectué. Cela devrait être appelé si le programme s'est terminé d'une manière vraiment, vraiment inattendue. Cela ne fera que signaler au système d'exploitation la fin anormale. Certains systèmes effectuent un vidage de mémoire dans ce cas.
  • std::terminateappelle le std::terminate_handlerqui appelle std::abortpar défaut.

25
C'est assez informatif: notamment je n'ai jamais réalisé que lancer une exception qui n'est gérée nulle part (par exemple: certains newlancers std::bad_allocet votre programme a oublié de capturer cette exception n'importe où) ne déroulera pas correctement la pile avant de terminer. Cela semble ridicule pour moi: il aurait été facile à essentiellement envelopper l'appel à mainun trivial try{- }catch(...){}bloc qui assurerait la pile de déroulement est bien fait dans ce cas, sans frais (je veux dire: les programmes ne pas utiliser ce ne paiera pas peine). Y a-t-il une raison particulière pour laquelle cela n'est pas fait?
Marc van Leeuwen

12
@MarcvanLeeuwen une raison possible est le débogage: vous voulez pénétrer dans le débogueur dès qu'une exception non gérée est levée. Dérouler la pile et effectuer le nettoyage effacerait le contexte de la cause du crash que vous souhaitez déboguer. Si aucun débogueur n'est présent, il peut être préférable de vider le core afin que l'analyse post-mortem puisse être effectuée.
Hugh Allen

11
Parfois, mon navigateur bogue (je blâme le flash) et consomme plusieurs gigaoctets de RAM et mon OS vide les pages du disque dur, ce qui ralentit tout. Lorsque je ferme le navigateur, il déroule correctement la pile, ce qui signifie que tous ces gigaoctets de RAM sont lus sur le disque dur et copiés dans la mémoire juste pour être libérés, ce qui prend environ une minute. J'aimerais tellement qu'ils l'aient utilisé à la std::abortplace pour que le système d'exploitation puisse libérer toute la mémoire, les sockets et les descripteurs de fichiers sans échanger pendant une minute.
nwp

2
@nwp, je comprends le sentiment. Mais tuer instantanément peut corrompre des fichiers, ne pas enregistrer mes onglets les plus récents, etc. :)
Paul Draper

2
@PaulDraper Je ne le considérerais certainement pas comme tolérable si un navigateur ne pouvait pas restaurer les onglets que j'avais ouverts avant une panne de courant. Bien sûr, si je venais d'ouvrir un onglet et qu'il n'avait pas encore eu le temps de le sauvegarder, il serait perdu. Mais à part ça, je dis qu'il n'y a aucune excuse pour le perdre.
kasperd

61

Comme Martin York l'a mentionné, la sortie n'effectue pas le nettoyage nécessaire comme le fait le retour.

Il est toujours préférable d'utiliser le retour au lieu de sortie. Si vous n'êtes pas dans main, où que vous souhaitiez quitter le programme, revenez d'abord à main.

Considérez l'exemple ci-dessous. Avec le programme suivant, un fichier sera créé avec le contenu mentionné. Mais si return est commenté et sortie non commentée (0), le compilateur ne vous garantit pas que le fichier aura le texte requis.

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

Non seulement cela, avoir plusieurs points de sortie dans un programme rendra le débogage plus difficile. N'utilisez exit que lorsque cela peut être justifié.


2
Que recommandez-vous pour obtenir ce comportement dans un programme un peu plus grand? Comment retournez-vous toujours à main proprement si quelque part plus profondément dans le code une condition d'erreur est déclenchée qui devrait quitter le programme?
Janusz

5
@Janusz, Dans ce cas, vous pouvez utiliser / lever des exceptions, sinon renvoyer une valeur prédéfinie, c'est-à-dire avoir une valeur de retour de la fonction, comme par exemple retourner 0 en cas de succès, 1 en cas d'échec, mais continuer l'exécution , -1 en cas d'échec & quitter le programme. Sur la base de la valeur de retour de la fonction, en cas d'échec, revenez simplement de la fonction principale après avoir effectué d'autres activités de nettoyage. Enfin, utilisez judicieusement la sortie, je ne veux pas l'éviter.
Narendra N

1
@NarendraN, le "nettoyage nécessaire" est vague - le système d'exploitation veillera (Windows / Linux) à ce que la mémoire et les descripteurs de fichiers soient correctement libérés. Quant à la sortie du fichier "manquant": Si vous insistez sur le fait que cela pourrait être un vrai problème, voir stackoverflow.com/questions/14105650/how-does-stdflush-work Si vous avez une condition d'erreur, une journalisation appropriée vous indique que votre programme atteint un état indéfini et vous pouvez définir un point d'arrêt juste avant votre point de journalisation. Comment cela rend-il le débogage plus difficile?
Markus

2
Notez que cette réponse a été fusionnée à partir de la question Comment quitter un programme C ++? - SO 1116493 . Cette réponse a été écrite environ 6 ans avant que cette question ne soit posée.
Jonathan Leffler

pour une utilisation C ++ moderne à la return (EXIT_SUCCESS);placereturn(0)
Jonas Stein

39

Appelez la std::exitfonction.   


2
Quels destructeurs d'objets sont appelés lorsque vous appelez cette fonction?
Rob Kennedy

37
exit () ne revient pas. Aucun déroulement de pile ne peut donc avoir lieu. Même les objets globaux ne sont pas détruits. Mais les fonctions enregistrées avec atexit () seront appelées.
Martin York

16
Veuillez ne pas appeler exit()lorsque le code de votre bibliothèque s'exécute dans mon processus hôte - ce dernier se fermera au milieu de nulle part.
2015

5
Notez que cette réponse a été fusionnée à partir de la question Comment quitter un programme C ++? - SO 1116493 . Cette réponse a été écrite environ 6 ans avant que cette question ne soit posée.
Jonathan Leffler

23

Les gens disent "appeler la sortie (code retour)", mais c'est une mauvaise forme. Dans les petits programmes, c'est bien, mais cela pose un certain nombre de problèmes:

  1. Vous finirez par avoir plusieurs points de sortie du programme
  2. Cela rend le code plus compliqué (comme l'utilisation de goto)
  3. Il ne peut pas libérer la mémoire allouée au moment de l'exécution

Vraiment, le seul moment où vous devez quitter le problème est avec cette ligne dans main.cpp:

return 0;

Si vous utilisez exit () pour gérer les erreurs, vous devez vous renseigner sur les exceptions (et les exceptions d'imbrication), en tant que méthode beaucoup plus élégante et sûre.


9
Dans un environnement multithread, une exception levée dans un thread différent ne sera pas gérée via main () - une communication manuelle entre les threads est nécessaire avant l'expiration du thread subordonné.
Steve Gilham

3
1. et 2. dépendent du programmeur, avec une journalisation appropriée, ce n'est pas un problème car l'exeuction s'arrête pour toujours. Quant à 3: c'est tout simplement faux, le système d'exploitation libérera la mémoire - peut-être en excluant les périphériques intégrés / en temps réel, mais si vous faites cela, vous connaissez probablement votre truc.
Markus

1
Notez que cette réponse a été fusionnée à partir de la question Comment quitter un programme C ++? - SO 1116493 . Cette réponse a été écrite environ 6 ans avant que cette question ne soit posée.
Jonathan Leffler

1
Si une erreur s'est produite, cependant, vous ne devez pas retourner 0. Vous devez retourner 1 (ou éventuellement une autre valeur, mais 1 est toujours un choix sûr).
Kef Schecter

14

return 0;placez-le où vous voulez int main()et le programme se fermera immédiatement.


the-nightman, @ evan-carslake, et tout le monde, j'ajouterais également que cette question SO # 36707 donne une discussion pour savoir si une déclaration de retour doit se produire n'importe où dans une routine. Ceci est une solution; cependant, selon la situation spécifique, je ne dirais pas que c'est la meilleure solution.
localhost

1
OP n'a rien dit à ce sujet, donc je pense qu'il est plutôt téméraire de supposer que le code était censé être à l'intérieur de la fonction main.
Marc van Leeuwen

@localhost Une déclaration de retour est complètement facultative dans toutes les situations, quelles que soient les conditions. Cependant, int main()c'est différent. Le programme utilise int main()pour commencer et se terminer. Il n'y a pas d'autre moyen de le faire. Le programme s'exécutera jusqu'à ce que vous retourniez vrai ou faux. Presque tout le temps, vous retournerez 1 ou true, pour indiquer que le programme s'est fermé correctement (ex: peut utiliser false si vous ne pouvez pas libérer de mémoire ou pour une raison quelconque.) Laisser le programme s'exécuter lui-même est toujours une mauvaise idée, exemple:int main() { int x = 2; int foo = x*5; std::cout << "blah"; }
Evan Carslake

@EvanCarslake Compris, si vous avez remarqué un commentaire que j'ai posté ailleurs pour cette question, je le connais bien int main; cependant, ce n'est pas toujours une bonne idée d'avoir plusieurs instructions de retour dans une routine principale. Une mise en garde potentielle consiste à améliorer la lisibilité du code; cependant, utiliser quelque chose comme un indicateur booléen qui change d'état pour empêcher certaines sections de code de s'exécuter dans cet état d'application. Personnellement, je trouve qu'une déclaration de retour commune rend la plupart des applications plus lisibles. Enfin, des questions sur le nettoyage de la mémoire et la fermeture correcte des objets d'E / S avant la sortie.
localhost

2
Vous ne retournez pas @EvanCarslake truede mainpour indiquer une terminaison correcte. Vous devez retourner zéro ou EXIT_SUCCESS(ou, si vous le souhaitez false, qui serait implicitement converti en zéro) pour indiquer une terminaison normale. Pour indiquer l'échec, vous pouvez revenir EXIT_FAILURE. Toute autre signification de code est définie par l'implémentation (sur les systèmes POSIX, cela signifierait un code d'erreur réel).
Ruslan

11

Le programme se terminera lorsque le flux d'exécution atteindra la fin de la fonction principale.

Pour le terminer avant cette date, vous pouvez utiliser la fonction exit (int status), où status est une valeur renvoyée à ce qui a démarré le programme. 0 indique normalement un état sans erreur


2
Notez que cette réponse a été fusionnée à partir de la question Comment quitter un programme C ++? - SO 1116493 . Cette réponse a été écrite environ 6 ans avant que cette question ne soit posée.
Jonathan Leffler

11

Soit renvoyez une valeur de votre, mainsoit utilisez la exitfonction. Les deux prennent un int. Peu importe la valeur que vous renvoyez, sauf si un processus externe surveille la valeur de retour.


3
Notez que cette réponse a été fusionnée à partir de la question Comment quitter un programme C ++? - SO 1116493 . Cette réponse a été écrite environ 6 ans avant que cette question ne soit posée.
Jonathan Leffler

11

Si vous avez une erreur quelque part au fond du code, lève une exception ou définit le code d'erreur. Il est toujours préférable de lever une exception au lieu de définir des codes d'erreur.


2
Notez que cette réponse a été fusionnée à partir de la question Comment quitter un programme C ++? - SO 1116493 . Cette réponse a été écrite environ 6 ans avant que cette question ne soit posée.
Jonathan Leffler

9

En général, vous utiliseriez la exit()méthode avec un état de sortie approprié .

Zéro signifierait une course réussie. Un état différent de zéro indique qu'une sorte de problème s'est produit. Ce code de sortie est utilisé par les processus parents (par exemple les scripts shell) pour déterminer si un processus s'est correctement exécuté.


1
utiliser la sortie PEUT signifier que vous avez un problème de conception. Si un programme fonctionne correctement, il devrait juste se terminer lorsqu'il est principal return 0;. Je suppose que exit()c'est comme assert(false);et ne devrait donc être utilisé que dans le développement pour les premiers problèmes de capture.
CoffeDeveloper

2
Notez que cette réponse a été fusionnée à partir de la question Comment quitter un programme C ++? - SO 1116493 . Cette réponse a été écrite environ 6 ans avant que cette question ne soit posée.
Jonathan Leffler

7

Au-delà d'appeler exit (error_code) - qui appelle des gestionnaires atexit, mais pas des destructeurs RAII, etc. - j'utilise de plus en plus d'exceptions.

De plus en plus mon programme principal ressemble

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);
    }
}

où primary_main dans où toutes les choses qui étaient à l'origine sont placées - c'est-à-dire que le principal original est renommé secondaire_main, et le stub principal ci-dessus est ajouté. C'est juste une subtilité, de sorte qu'il n'y a pas trop de code entre le plateau et la prise principale.

Si vous le souhaitez, interceptez d'autres types d'exceptions.
J'aime bien attraper les types d'erreur de chaîne, comme std :: string ou char *, et les imprimer dans le gestionnaire de capture en principal.

L'utilisation d'exceptions comme celle-ci permet au moins d'appeler les destructeurs RAII, afin qu'ils puissent faire le nettoyage. Ce qui peut être agréable et utile.

Dans l'ensemble, la gestion des erreurs C - sortie et signaux - et la gestion des erreurs C ++ - exceptions try / catch / throw - jouent ensemble au mieux de manière incohérente.

Ensuite, où vous détectez une erreur

throw "error message"

ou un type d'exception plus spécifique.


Soit dit en passant: je suis bien conscient que l'utilisation d'un type de données comme une chaîne, qui peut impliquer une allocation de mémoire dynamique, n'est pas une bonne idée dans ou autour d'un gestionnaire d'exceptions pour les exceptions qui peuvent être liées à un manque de mémoire. Les constantes de chaîne de style C ne sont pas un problème.
Krazy Glew

1
Inutile d'appeler exitvotre programme. Puisque vous y êtes main, vous pouvez simplement return exitCode;.
Ruslan

-1

Si votre instruction if est en boucle, vous pouvez utiliser

 break; 

Si vous voulez échapper du code et continuer à boucler Utilisez:

continuer;

Si votre instruction if n'est pas dans la boucle, vous pouvez utiliser:

 return 0;

Or 




  exit();

-2

La exit()fonction Dude ... est définie sous stdlib.h

Vous devez donc ajouter un préprocesseur.

Mettez include stdlib.hdans la section d'en-tête

Ensuite, utilisez exit();où vous voulez, mais n'oubliez pas de mettre un nombre entier entre parenthèses de sortie.

par exemple:

exit(0);

-2

Si la condition pour laquelle je teste est vraiment une mauvaise nouvelle, je fais ceci:

*(int*) NULL= 0;

Cela me donne un joli coredump d'où je peux examiner la situation.


4
Cela peut être optimisé, car il provoque un comportement indéfini. En fait, toute la branche d'exécution ayant une telle instruction peut être effacée par le compilateur.
Ruslan

-2

Pour rompre une condition, utilisez le retour (0);

Donc, dans votre cas, ce serait:

    if(x==1)
    {
        return 0;
    }
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.