Devrions-nous utiliser exit () dans C?

Il y a une question sur l’utilisation de exit en C ++. La réponse explique que ce n’est pas une bonne idée principalement à cause de RAII, par exemple, si exit est appelé quelque part dans le code, les destructeurs d’objects ne seront pas appelés, donc si un destructeur devait écrire des données dans un fichier, arriver, parce que le destructeur n’a pas été appelé.

J’étais intéressé comment est cette situation en C. Des problèmes similaires sont-ils également applicables en C? Je pensais que dans C nous n’utilisons pas de constructeurs / destructeurs, la situation pourrait être différente dans C. Donc, est-il correct d’utiliser exit dans C?

J’ai vu des fonctions telles que ci-dessous, que je trouve bien à utiliser dans certains cas, mais était intéressé si nous avions des problèmes similaires en C avec l’utilisation de exit , comme décrit ci-dessus avec C ++? (ce qui rendrait l’utilisation de fonctions telles que ci-dessous pas une bonne idée.)

 void die(const char *message) { if(errno) { perror(message); } else { printf("ERROR: %s\n", message); } exit(1); } 

Plutôt que d’ abort() , la fonction exit() dans C est considérée comme une sortie “gracieuse”.

De C11 (N1570) 7.22.4.4/p2 La fonction de sortie (mise en évidence du mien):

La fonction de exit provoque la fin normale du programme.

La norme indique également au 7.22.4.4/p4 que:

Ensuite, tous les stream ouverts avec des données tamponnées non écrites sont vidés , tous les stream ouverts sont fermés et tous les fichiers créés par la fonction tmpfile sont supprimés.

Il est également intéressant de regarder les fichiers 7.21.3 / p5:

Si la fonction main revient à son appelant d’origine ou si la fonction de exit est appelée, tous les fichiers ouverts sont fermés (tous les stream de sortie sont donc vidés) avant la fin du programme. Les autres chemins d’access à la terminaison du programme, tels que l’appel de la fonction d’ abort , n’ont pas besoin de fermer tous les fichiers correctement.

Cependant, comme mentionné dans les commentaires ci-dessous, vous ne pouvez pas supposer qu’il couvrira toutes les autres ressources , vous devrez donc peut-être recourir à atexit() et définir des rappels pour leur publication individuellement. En fait, c’est exactement ce que atexit() est censé faire, comme il est dit au 7.22.4.2/p2 La fonction atexit :

La fonction atexit enregistre la fonction pointée par func , à appeler sans arguments à la fin normale du programme.

Notamment, le standard C ne dit pas précisément ce qui doit arriver aux objects ayant une durée de stockage allouée (c.-à-d. malloc() ), vous obligeant ainsi à savoir comment cela se fait sur une implémentation particulière. Pour les systèmes d’exploitation modernes, orientés hôte, il est probable que le système s’en occupe, mais vous pouvez toujours vous en occuper vous-même afin de faire taire les débogueurs de mémoire tels que Valgrind .

Oui, vous pouvez utiliser exit en C.

Pour assurer tous les tampons et les arrêts ordonnés, il est recommandé d’utiliser cette fonction atexit , plus d’informations ici

Un exemple de code serait comme ceci:

 void cleanup(void){ /* example of closing file pointer and free up memory */ if (fp) fclose(fp); if (ptr) free(ptr); } int main(int argc, char **argv){ /* ... */ atexit(cleanup); /* ... */ return 0; } 

Maintenant, chaque fois que exit est appelé, le cleanup la fonction sera exécuté, ce qui peut contenir un arrêt en douceur, le nettoyage des tampons, de la mémoire, etc.

Vous n’avez pas de constructeurs et de destructeurs, mais vous pouvez avoir des ressources (par exemple des fichiers, des stream, des sockets) et il est important de les fermer correctement. Un tampon ne pouvait pas être écrit de manière synchrone, donc quitter le programme sans fermer correctement la ressource en premier pourrait entraîner une corruption.

Utiliser exit() est OK

Les deux principaux aspects de la conception de code qui n’ont pas encore été mentionnés sont les «threads» et les «bibliothèques».

Dans un programme à thread unique, dans le code que vous écrivez pour implémenter ce programme, utiliser exit() convient bien. Mes programmes l’utilisent régulièrement lorsque quelque chose ne va pas et que le code ne va pas se rétablir.

Mais…

Cependant, appeler exit() est une action unilatérale qui ne peut pas être annulée. C’est pourquoi les «threading» et les «bibliothèques» nécessitent une reflection approfondie.

Programmes fileté

Si un programme est multi-thread, alors utiliser exit() est une action dramatique qui termine tous les threads. Il sera probablement inapproprié de quitter l’ensemble du programme. Il peut être approprié de quitter le thread, en signalant une erreur. Si vous êtes au courant de la conception du programme, alors cette sortie unilatérale est peut-être admissible, mais en général, ce ne sera pas acceptable.

Code de la bibliothèque

Et que la clause “consciente de la conception du programme” s’applique également au code dans les bibliothèques. Il est très rare qu’une fonction de bibliothèque à usage général appelle exit() . Vous seriez à juste titre contrarié si l’une des fonctions standard de la bibliothèque C échouait à cause d’une erreur. (Évidemment, les fonctions comme exit() , _Exit() , quick_exit() , abort() sont pas destinées à renvoyer, c’est différent.) Les fonctions de la bibliothèque C ne peuvent donc pas échouer ou renvoyer une indication d’erreur. . Si vous écrivez du code pour aller dans une bibliothèque à usage général, vous devez examiner attentivement la stratégie de gestion des erreurs pour votre code. Il devrait être compatible avec les stratégies de gestion des erreurs des programmes avec lesquels il est destiné à être utilisé, ou la gestion des erreurs peut être configurée.

J’ai une série de fonctions de bibliothèque (dans un paquet avec l’en-tête "stderr.h" , un nom qui se glisse sur les couches minces), destinées à sortir lorsqu’elles sont utilisées pour le rapport d’erreurs. Ces fonctions sortent par conception. Il existe une série de fonctions connexes dans le même package qui signalent des erreurs et ne se terminent pas. Les fonctions existantes sont bien entendu implémentées en termes de fonctions non existantes, mais il s’agit d’un détail d’implémentation interne.

J’ai beaucoup d’autres fonctions de bibliothèque, et bon nombre d’entre elles reposent sur le code "stderr.h" pour signaler les erreurs. C’est une décision de conception que j’ai prise et avec laquelle je suis d’accord. Mais lorsque les erreurs sont rapscopes avec les fonctions qui sortent, cela limite l’utilité générale du code de la bibliothèque. Si le code appelle les fonctions de rapport d’erreurs qui ne se terminent pas, les chemins de code principaux de la fonction doivent gérer les retours d’erreur de manière sûre – les détecter et transmettre une indication d’erreur au code d’appel.


Le code de mon package de rapport d’erreurs est disponible dans mon référentiel SOQ (Stack Overflow Questions) sur GitHub en tant que fichiers stderr.c et stderr.h dans le sous-répertoire src / libsoq .

Une des raisons pour éviter la exit dans des fonctions autres que main() est la possibilité que votre code soit pris hors contexte. Rappelez-vous que exit est un type de stream de contrôle non local . Comme des exceptions insatiables.

Par exemple, vous pouvez écrire des fonctions de gestion du stockage qui se terminent par une erreur de disque critique. Puis quelqu’un décide de les déplacer dans une bibliothèque. Quitter une bibliothèque est quelque chose qui provoque la sortie du programme appelant dans un état incohérent pour lequel il ne peut pas être préparé.

Ou vous pouvez l’exécuter sur un système intégré. Il n’y a nulle part où sortir, le tout s’exécute dans une boucle while(1) dans main() . Il pourrait même ne pas être défini dans la bibliothèque standard.

Selon ce que vous faites, exit est peut-être le moyen le plus logique de sortir d’un programme en C. Je sais que c’est très utile pour vérifier que les chaînes de rappels fonctionnent correctement. Prenez cet exemple de rappel que j’ai utilisé récemment:

 unsigned char cbShowDataThenExit( unsigned char *data, unsigned short dataSz,unsigned char status) { printf("cbShowDataThenExit with status %X (dataSz %d)\n", status, dataSz); printf("status:%d\n",status); printArray(data,dataSz); cleanUp(); exit(0); } 

Dans la boucle principale, je mets tout en place pour ce système, puis attend dans une boucle (1). Il est possible de créer un indicateur global pour quitter la boucle while à la place, mais c’est simple et fait ce qu’il doit faire. Si vous avez affaire à des tampons ouverts tels que des fichiers et des périphériques, vous devez les nettoyer avant de les fermer pour plus de cohérence.

C’est terrible dans un gros projet quand n’importe quel code peut sortir sauf pour coredump. La trace est très importante pour maintenir un serveur en ligne.