instruction return vs exit () dans main ()

197

Dois-je utiliser exit()ou simplement des returndéclarations main()? Personnellement, je préfère les returndéclarations car je pense que c'est comme lire n'importe quelle autre fonction et le contrôle de flux lorsque je lis le code est fluide (à mon avis). Et même si je veux refactoriser la main()fonction, avoir returnsemble être un meilleur choix que exit().

Fait exit()quelque chose de spécial qui returnne le fait pas?

Srikanth
la source

Réponses:

277

En fait, il y a une différence, mais c'est subtil. Il a plus d'implications pour C ++, mais les différences sont importantes.

Quand j'appelle returnà main(), Destructeurs seront appelés à mes objets scope localement. Si j'appelle exit(), aucun destructeur ne sera appelé pour mes objets de portée locale! Relisez ça. exit() ne revient pas . Cela signifie qu'une fois que je l'appelle, il n'y a «aucun backsies». Les objets que vous avez créés dans cette fonction ne seront pas détruits. Souvent, cela n'a aucune implication, mais parfois, comme la fermeture de fichiers (vous voulez sûrement que toutes vos données soient vidées sur le disque?).

Notez que les staticobjets seront nettoyés même si vous appelez exit(). Notez enfin que si vous utilisez abort(), aucun objet ne sera détruit. C'est-à-dire qu'aucun objet global, aucun objet statique et aucun objet local n'auront leurs destructeurs appelés.

Procédez avec prudence lorsque vous privilégiez la sortie au retour.

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

Mémoire libre
la source
1
abort () se termine avec une condition d'erreur (code de sortie différent de zéro) et peut même être un noyau. Si vous devez quitter sans appeler les destructeurs statiques, utilisez _exit.
7
@Mike: il y a une différence entre les tampons de fichiers de la bibliothèque C et les objets de flux de fichiers C ++. exit () - faisant partie de la bibliothèque C - a été conçu pour coordonner et vider la première, mais peut contourner la seconde: même le contenu fstream C ++ standard n'est pas vidé sur le disque (essayez - je l'ai fait, il échoue avec Linux / GCC), et les types définis par l'utilisateur qui ont des E / S tamponnées ne peuvent pas non plus être vus.
Tony Delroy
9
Remarque: La déclaration: aucun destructeur ne sera appelé pour mes objets de portée locale! n'est plus vrai pour C ++ 11: - Les objets associés au thread actuel avec une durée de stockage de thread sont détruits (C ++ 11 uniquement). cplusplus.com/reference/cstdlib/exit
Ilendir
7
Cela signifie que thread_localles destructeurs d'objets seront appelés. Les destructeurs d'autres objets locaux ne sont toujours pas appelés. ideone.com/Y6Dh3f
HolyBlackCat
3
BTW, et juste pour être pédant et parce que cette réponse peut toujours être déroutante pour les lecteurs utilisant C: Pour C, le problème de la exit()fermeture des fichiers proprement est en fait faux. Le seul moment où les données peuvent ne pas être vidées est dans le cas contraire: c'est-à-dire si l'on utilise returndepuis main()et que l'on a appelé setbuf()ou setvbuf()avec un tampon déclaré comme stockage automatique dans main()(comme expliqué dans la réponse de R. ci-dessous). C'est vraiment dommage que cette question soit balisée avec C et C ++ (et avec le style de codage - ce n'est pas un problème de style!).
Greg A. Woods du
25

Une autre différence: exitc'est une fonction de bibliothèque standard, vous devez donc inclure des en-têtes et un lien avec la bibliothèque standard. Pour illustrer (en C ++), ceci est un programme valide:

int main() { return 0; }

mais pour l'utiliser, exitvous aurez besoin d'un include:

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

De plus, cela ajoute une hypothèse supplémentaire: que l'appel exitde maina les mêmes effets secondaires que le retour à zéro. Comme d'autres l'ont souligné, cela dépend du type d'exécutable que vous créez (c'est-à-dire qui appelle main). Codez-vous une application qui utilise le C-runtime? Un plugin Maya? Un service Windows? Un chauffeur? Chaque cas nécessitera des recherches pour voir si exitest équivalent à return. À exitmon humble avis, utiliser quand vous voulez vraiment dire return rend le code plus confus. OTOH, si vous voulez vraiment dire exit , alors utilisez-le par tous les moyens.

jwfearn
la source
16

Il y a au moins une raison de préférer exit: si l'un de vos atexitgestionnaires fait référence à des données de durée de stockage automatique dans main, ou si vous avez utilisé setvbufou setbufpour affecter à l'un des flux standard un tampon de durée de stockage automatique dans main, puis le retour de mainproduit comportement indéfini, mais l'appel exitest valide.

Une autre utilisation potentielle (généralement réservée aux programmes de jouets, cependant) est de quitter un programme avec des invocations récursives de main.

R .. GitHub ARRÊTEZ D'AIDER LA GLACE
la source
1
@Seb, il n'y a rien de spécial main()- c'est juste une fonction comme les autres. D'un autre côté, puisqu'il a une mention spéciale dans la norme, la norme doit faire assez attention à la façon dont elle la définit main()et aux choses qui lui sont chères et proches. Cependant, en fin de compte, bien que la norme n'exige pas (et ne doit pas obliger) les compilateurs à faire quelque chose de spécial concernant le stockage automatique dans main(). Veuillez prendre soin de lire la note de bas de page # 11 sous le paragraphe auquel vous avez fait référence dans votre commentaire.
Greg A. Woods,
1
@ GregA.Woods Intéressant. Il semble qu'il y ait un texte normatif en contradiction avec un texte informatif. Selon les directives ISO / CEI , la référence normative est considérée comme "indispensable", où-comme l'information n'est considérée que comme complémentaire ... De plus, l'utilisation du mot "volonté" pour exprimer une exigence n'est pas valide; conformément au document susmentionné (annexe H). En résumé, le texte informatif est très certainement invalide.
autiste
2
@Seb: L'intention n'est évidemment pas de passer outre les exigences sur le comportement du stockage automatique et la note de bas de page a évidemment été écrite pour clarifier cela. Oui, il y a une formulation imprécise et médiocre dans la norme C. Quiconque l'a lu le sait. Nous savons également que le comité ne règle généralement pas de tels problèmes parce que l'intention est déjà évidente.
R .. GitHub STOP HELPING ICE
1
@Seb: Ce n'est pas un débat ou un procès pour prouver que vous avez raison. L'objectif devrait être d'obtenir une compréhension claire de ce qu'est le langage C réel (tel que prévu et tel qu'il est implémenté) et de l'exprimer dans des réponses utiles aux lecteurs. Le texte normatif est subtilement erroné (contrairement à l'intention de ce qu'il était censé exprimer) d'une manière qui est essentiellement fixée par la note de bas de page. Si vous n'êtes pas satisfait de cela, soumettez un rapport de défaut, mais n'attendez pas de réponse. C'est comme ça que le WG14 roule ...
R .. GitHub STOP STOPINGING ICE
3
@Seb: Vous semblez croire que le langage C peut être compris en interprétant le texte en langage naturel de la norme comme s'il était complètement rigoureux. Ce n'est tout simplement pas possible. La spécification contient des erreurs, et WG14 ne perd pas son temps à réécrire des trucs quand une simple note de bas de page précise qu'ils savent déjà qu'ils ont fait une erreur mais que le lecteur peut en comprendre le sens.
R .. GitHub STOP HELPING ICE
5

J'utilise toujours returnparce que le prototype standard pour main()dit qu'il retourne un int.

Cela dit, certaines versions des normes accordent mainun traitement spécial et supposent qu'elle renvoie 0 s'il n'y a pas de returndéclaration explicite . Étant donné le code suivant:

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

G ++ génère uniquement un avertissement pour foo()et ignore le retour manquant de main:

% g++ -Wall -c foo.cc
foo.cc: In function int foo()’:
foo.cc:1: warning: control reaches end of non-void function
Alnitak
la source
Je ne connais pas C, mais le standard C ++ spécifie que si vous ne retournez pas de valeur dans main, il est supposé renvoyer 0.
Jason Baker
Il semble que C99 soit le même: faq.cprogramming.com/cgi-bin/…
Jason Baker
2
C99 et C ++ renvoient 0 s'il n'y a pas de déclaration de retour, C90 non.
d0k
Ce n'est pas parce qu'une fonction est déclarée comme ayant une valeur de retour que vous devez utiliser returnpour mettre fin à son exécution. L'appel exit()est également un moyen valide, et parfois nécessaire, de mettre fin à l'exécution d'une fonction. En effet, comme moi et d'autres l'ont décrit ailleurs, appeler exit()même de main()transmet une intention beaucoup plus claire de quitter l'intégralité du processus, préserve le stockage automatique jusqu'à la fin du processus et facilite la maintenance lors de la refactorisation future du code. C en utilisant returndans main()lorsque l'intention est de mettre fin au processus est donc sans doute une mauvaise pratique.
Greg A. Woods
@ GregA.Woods c'est votre opinion, mais ne mérite guère un vote négatif! Ce que j'ai écrit ci-dessus est complètement cohérent avec la norme , alors que votre argument n'est que sémantique.
Alnitak
5

Je FORTEMENT deuxième le commentaire par R. sur l' utilisation de la sortie () afin d'éviter le stockage automatique main()remis en état avant le programme en fait se termine. Une return X;instruction in main()n'est pas exactement équivalente à un appel à exit(X);, car le stockage dynamique de main()disparaît lors du main()retour, mais il ne disparaît pas si un appel à exit()est effectué à la place.

De plus, en C ou tout autre langage de type C, une returninstruction indique fortement au lecteur que l'exécution se poursuivra dans la fonction appelante, et bien que cette poursuite de l'exécution soit généralement techniquement vraie si vous comptez la routine de démarrage C qui a appelé votre main()fonction, ce n'est pas exactement ce que vous voulez dire lorsque vous voulez mettre fin au processus.

Après tout, si vous souhaitez terminer votre programme à partir de toute autre fonction, sauf que main()vous devez appeler exit(). Le faire de manière cohérente main()rend également votre code beaucoup plus lisible, et il est également beaucoup plus facile pour quiconque de re-factoriser votre code; c'est-à-dire que le code copié depuis main()une autre fonction ne se comportera pas mal en raison de returndéclarations accidentelles qui auraient dû être des exit()appels.

Donc, en combinant tous ces points ensemble, la conclusion est que c'est une mauvaise habitude , au moins pour C, d'utiliser une returndéclaration pour terminer le programme main().

Greg A. Woods
la source
Vous pourriez trouver 5.1.2.2.3p1 de la norme C intéressant ...
autiste
Cette réponse mérite une attention particulière pour les programmes C, comme indiqué contextuellement dans la réponse. Pour une utilisation avec C ++, il doit être soigneusement pesé par rapport à toutes les mises en garde mentionnées précédemment. Pour C ++, je suggère d'éviter exit()en général, mais utilisez-le si une throwou des abort()alternatives ne fonctionnent pas dans un contexte spécifique. Mais surtout évitez exit()en principal, et utilisez plutôt le retour en principal comme pratique typique.
Eljay
5

Exit () fait-il quelque chose de spécial que «return» ne fait pas?

Avec certains compilateurs pour les plates-formes inhabituelles, exit()pourrait traduire son argument en valeur de sortie de votre programme tandis qu'un retour de main()pourrait simplement passer la valeur directement à l'environnement hôte sans aucune traduction.

La norme requiert un comportement identique dans ces cas (en particulier, elle indique que renvoyer quelque chose qui est intcompatible à partir de main()devrait être équivalent à appeler exit()avec cette valeur). Le problème est que différents systèmes d'exploitation ont des conventions différentes pour interpréter les valeurs de sortie. Sur de nombreux systèmes (BEAUCOUP!), 0 signifie succès et tout le reste est un échec. Mais sur, par exemple, VMS, les valeurs impaires signifient le succès et même les valeurs signifient l'échec. Si vous renvoyiez 0 de main(), un utilisateur VMS verrait un méchant message concernant une violation d'accès. Il n'y avait pas réellement de violation d'accès - c'était simplement le message standard associé au code d'échec 0.

Puis l'ANSI est venu et a béni EXIT_SUCCESSet EXIT_FAILUREcomme arguments vous pourriez passer exit(). La norme indique également que exit(0)devrait comporter de manière identique à exit(EXIT_SUCCESS), de sorte que la plupart des implémentations définissent EXIT_SUCCESSà 0.

La norme, par conséquent, vous met dans une liaison sur VMS, car elle ne laisse aucun moyen standard de renvoyer un code d' échec qui se trouve avoir la valeur 0.

Le compilateur VAX / VMS C de l'ère du début des années 1990 n'a donc pas interprété la valeur de retour main(), il a simplement renvoyé toute valeur à l'environnement hôte. Mais si vous avez utiliséexit() il ferait ce que la norme exigeait: traduire EXIT_SUCCESS(ou 0) en un code de réussite et EXIT_FAILUREen un code d'échec générique. Pour utiliser EXIT_SUCCESS, vous deviez passer à exit(), on ne pouvait pas retourner à partir main(). Je ne sais pas si des versions plus modernes de ce compilateur ont préservé ce comportement.

Un programme C portable ressemblait à ceci:

#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 */
}

En plus: Si je me souviens bien, la convention VMS pour les valeurs de sortie est plus nuancée qu'impaire / paire. Il utilise en fait quelque chose comme les trois bits bas pour coder un niveau de gravité. D'une manière générale, cependant, les niveaux de gravité impairs indiquaient le succès ou des informations diverses et les pairs même indiquaient des erreurs.

Adrian McCarthy
la source
Certains anciens compilateurs pré-ANSI pourraient avoir traité la valeur returnedpar maindifférente de la valeur passée exit- mais la norme dit expressément: « Si le type de retour de la mainfonction est un type compatible avec intun retour de l'appel initial à la mainfonction est équivaut à appeler la exitfonction avec la valeur renvoyée par la mainfonction comme argument ". C'est C11; C89 / C90 avait presque le même libellé.
Keith Thompson
En effet. Néanmoins, certains compilateurs de l'ère ANSI n'ont pas obtenu ce droit et ont exigé une utilisation explicite de exit pour obtenir la valeur de retour correcte retournée à l'environnement hôte. Étant donné que la norme (même dans ce cas) requiert que 0 soit traité de la même manière que EXIT_SUCCESS, il n'y avait aucun moyen de renvoyer un état d' échec spécifique à la plate-forme avec la valeur 0, ce qui peut être la raison pour laquelle certains des compilateurs de l'époque traitaient le retour de la main et exit()différemment.
Adrian McCarthy
Avez-vous une citation pour cela? Un problème distinct est de savoir si les compilateurs actuels ont ce bogue particulier. Votre réponse est formulée au présent.
Keith Thompson
Voilà une critique juste. J'ai changé le libellé pour limiter la portée au cas spécifique que je connais.
Adrian McCarthy
0

En C, le retour mainest exactement le même que l'appel exitavec la même valeur.

La section 5.1.2.2.3 de la norme C stipule:

Si le type de retour de la fonction principale est un type compatible avec int, un retour de l'appel initial à la fonction principale équivaut à appeler la fonction exit avec la valeur renvoyée par la fonction principale comme argument ; 11) atteindre le} qui termine la fonction principale renvoie une valeur de 0. Si le type de retour n'est pas compatible avec int, l'état de terminaison renvoyé à l'environnement hôte n'est pas spécifié.

Les règles pour C ++ sont un peu différentes comme mentionné dans d'autres réponses.

dbush
la source
-1

Il y a en fait une différence entre exit(0)et return(0)in main- lorsque votre mainfonction est appelée plusieurs fois.

Le programme suivant

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

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

Courir comme

./program 0 0 0 0

Se traduira par la sortie suivante:

00000

Cependant celui-ci:

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

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

N'imprime rien, quels que soient les arguments.

Si vous êtes sûr que personne n'appellera jamais votre mainexplicitement, ce n'est pas techniquement une grande différence en général, mais conserver un code plus clair exitserait beaucoup mieux. Si, pour une raison quelconque, vous souhaitez appeler main, vous devez l'adapter à vos besoins.

En parlant de C.

radrow
la source