Un programme C ++ doit-il intercepter toutes les exceptions et empêcher les exceptions de se propager au-delà de main ()?

29

On m'a déjà dit qu'un programme C ++ devrait finalement intercepter toutes les exceptions. Le raisonnement donné à l'époque était essentiellement que les programmes qui permettent aux exceptions de se propager en dehors d' main()entrer dans un état de zombie étrange. On me l'a dit il y a plusieurs années et, rétrospectivement, je crois que le phénomène observé était dû à la longue génération de décharges de noyau exceptionnellement grandes à partir du projet en question.

À l'époque, cela semblait bizarre mais convaincant. Il était totalement insensé que C ++ "punisse" les programmeurs pour ne pas avoir intercepté toutes les exceptions, mais les preuves dont je disposais semblaient corroborer cela. Pour le projet en question, les programmes qui ont levé des exceptions non capturées semblaient entrer dans un état de zombie étrange - ou comme je soupçonne que la cause était maintenant, un processus au milieu d'un vidage de mémoire indésirable est exceptionnellement difficile à arrêter.

(Pour quiconque se demande pourquoi cela n'était pas plus évident à l'époque: le projet a généré une grande quantité de sortie dans plusieurs fichiers à partir de plusieurs processus qui ont effectivement masqué toute sorte de aborted (core dumped)message et dans ce cas particulier, l'examen post mortem des vidages de mémoire n'a pas été Ce n'est pas une technique de débogage importante, donc les vidages de mémoire n'ont pas été beaucoup réfléchis. Les problèmes avec un programme ne dépendent généralement pas de l'état accumulé à partir de nombreux événements au fil du temps par un programme de longue durée, mais plutôt des entrées initiales d'un programme de courte durée (< 1 heure), il était donc plus pratique de simplement réexécuter un programme avec les mêmes entrées à partir d'une version de débogage ou dans un débogueur pour obtenir plus d'informations.)

Actuellement, je ne sais pas s'il y a un avantage ou un inconvénient majeur à intercepter des exceptions uniquement dans le but d'empêcher les exceptions de quitter main().

Le petit avantage main()auquel je peux penser pour permettre aux exceptions de se propager dans le passé est qu'il provoque l' std::exception::what()impression du résultat sur le terminal (au moins avec les programmes compilés par gcc sous Linux). D'un autre côté, cela est trivial à réaliser en capturant à la place toutes les exceptions dérivées de std::exceptionet en imprimant le résultat de std::exception::what()et s'il est souhaitable d'imprimer un message à partir d'une exception qui ne dérive pas, std::exceptionil doit être capturé avant de quitter main()afin d'imprimer le message.

Le désavantage modeste auquel je peux penser pour permettre aux exceptions de remonter dans le passé main()est que des vidages de mémoire indésirables peuvent être générés. Pour un processus utilisant une grande quantité de mémoire, cela peut être assez gênant et le contrôle du comportement de vidage du noyau à partir d'un programme nécessite des appels de fonction spécifiques au système d'exploitation. D'un autre côté, si un vidage de mémoire et une sortie sont souhaités, cela peut être réalisé à tout moment en appelant std::abort()et une sortie sans vidage de mémoire peut être obtenue à tout moment en appelant std::exit().

Pour l'anecdote, je ne pense pas avoir jamais vu le what(): ...message par défaut imprimé par un programme largement distribué lors du plantage.

Quels sont, le cas échéant, les arguments solides pour ou contre l’autorisation de voir les exceptions C ++ se multiplier main()?

Edit: Il y a beaucoup de questions générales sur la gestion des exceptions sur ce site. Ma question concerne spécifiquement les exceptions C ++ qui ne peuvent pas être gérées et qui ont réussi jusqu'à main()- peut-être qu'un message d'erreur peut être imprimé mais c'est une erreur d'arrêt immédiat.

Praxéolitique
la source
@gnat Ma question est un peu plus précise. Il s'agit d'exceptions C ++ qui ne peuvent pas être gérées plutôt que de gérer des exceptions dans n'importe quel langage de programmation en toutes circonstances.
Praxeolitic
Pour les programmes multithreads, les choses peuvent être plus complexes ou plus exotiques (sauf exceptions)
Basile Starynkevitch
1
Les gars du logiciel pour Ariane 5 aimeraient certainement avoir pris toutes les exceptions lors du premier lancement ...
Eric Towers

Réponses:

25

Un problème lié au fait de laisser les exceptions dépasser le cadre principal est que le programme se terminera par un appel std::terminateauquel le comportement par défaut est d'appeler std::abort. Il n'est défini par l'implémentation que si le déroulement de la pile est effectué avant l'appel terminateafin que votre programme puisse se terminer sans appeler un seul destructeur! Si vous avez une ressource qui avait vraiment besoin d'être restaurée par un appel de destructeur, vous êtes dans un cornichon ...

erlc
la source
12
Si vous avez une ressource qui devait vraiment être restaurée par un appel de destructeur, vous êtes dans un cornichon ... => Étant donné que (malheureusement) C ++ est assez sujet aux plantages, et c'est sans mentionner que le système d'exploitation pourrait décider de tuer votre programme à tout moment (OOM killer dans Unix par exemple), votre programme (et son environnement) doivent être adaptés pour supporter les plantages.
Matthieu M.
@MatthieuM. Êtes-vous en train de dire que les destructeurs ne sont pas un bon endroit pour le nettoyage des ressources qui devrait se produire même lors d'un crash?
Praxeolitic
6
@Praxeolitic: Je dis que tous les plantages ne déroulent pas la pile, par exemple std::abortne le font pas et donc les assertions qui ont échoué ne le sont pas non plus. Il est également intéressant de noter que sur les systèmes d'exploitation modernes, le système d'exploitation lui-même nettoiera de nombreuses ressources (mémoire, descripteurs de fichiers, ...) qui sont liées à l'ID de processus. Enfin, je faisais également allusion à "Défense en profondeur": il n'est pas fiable de s'attendre à ce que tous les autres processus soient à l'épreuve des bogues et libéreront toujours les ressources qu'ils ont acquises (sessions, fin de l'écriture des fichiers, ...) que vous devez planifier ça ...
Matthieu M.
3
@Praxeolitic Non, votre logiciel ne doit pas corrompre les données lors d'une panne de courant. Et si vous pouvez gérer cela, vous pouvez également réussir à ne pas corrompre les données après une exception non gérée.
user253751
1
@Praxeolitic: En fait, cela existe. Vous aurez envie d'écouter le WM_POWERBROADCASTmessage. Cela ne fonctionne que si votre ordinateur est alimenté par batterie (si vous utilisez un ordinateur portable ou un onduleur).
Brian
28

La principale raison pour ne pas laisser les exceptions s'échapper mainest parce que sinon vous perdez toute possibilité de contrôler la façon dont le problème est signalé à vos utilisateurs.

Pour un programme qui n'est pas destiné à être utilisé pendant longtemps ou largement diffusé, il peut être acceptable que des erreurs inattendues soient signalées de quelque manière que le système d'exploitation décide de le faire (par exemple, en affichant une boîte de dialogue d'erreur sur Windows sous Windows). ).

Pour les programmes que vous vendez ou qui sont fournis au grand public par une organisation qui a une réputation à défendre, il est généralement préférable de signaler de manière agréable que vous avez rencontré un problème inattendu et d'essayer d'enregistrer autant de données de l'utilisateur que possible. Ne pas perdre une demi-journée de travail de votre utilisateur et ne pas tomber en panne de façon inattendue, mais l'arrêt semi-gracieux est généralement bien meilleur pour la réputation de votre entreprise que l'alternative.

Bart van Ingen Schenau
la source
Êtes-vous sûr que le comportement par défaut d'une exception C ++ qui reste main() a beaucoup à voir avec le système d'exploitation? L'OS pourrait ne rien savoir du C ++. Je suppose que c'est déterminé par le code que le compilateur insère quelque part dans le programme.
Praxeolitic
5
@Praxeolitic: C'est plus que le système d'exploitation définit les conventions et que le compilateur génère du code pour respecter ces conventions lorsqu'un programme se termine de façon inattendue. L'essentiel est que la fin inattendue du programme doit être évitée autant que possible.
Bart van Ingen Schenau
Vous perdez uniquement le contrôle dans le cadre de std C ++. Une manière spécifique à l'implémentation de gérer ces "plantages" devrait être disponible. C ++ ne fonctionne généralement pas dans le vide.
Martin Ba
Cette boîte de dialogue d'erreur directe peut être activée / désactivée dans le registre pour ce que cela vaut - mais vous auriez besoin d'un contrôle du registre pour ce faire - et la plupart des programmes ne s'exécutent pas en mode kiosque (ayant pris le relais l'OS).
PerryC
11

TL; DR : Que dit la spécification?


Un détour technique ...

Lorsqu'une exception est levée et qu'aucun gestionnaire n'est prêt pour cela:

  • c'est l'implémentation définie que la pile soit déroulée ou non
  • std::terminate est appelé, qui par défaut abandonne
  • selon la configuration de votre environnement, l'interruption peut ou non laisser un rapport de plantage

Ce dernier peut être utile pour des bugs très peu fréquents (car les reproduire est un processus qui fait perdre du temps).


La capture ou non de toutes les exceptions est, en fin de compte, une question de spécification:

  • est-il spécifié comment signaler les erreurs des utilisateurs? (valeur invalide, ...)
  • est-il spécifié comment signaler les erreurs fonctionnelles? (répertoire cible manquant, ...)
  • est-il précisé comment signaler les erreurs techniques? (assertion tirant, ...)

Pour tout programme de production, cela doit être spécifié et vous devez suivre la spécification (et peut-être faire valoir qu'elle doit être modifiée).

Pour que les programmes rapidement regroupés ne soient utilisés que par des techniciens (vous, vos coéquipiers), tout va bien. Je recommande de le laisser planter et de configurer l'environnement pour obtenir un rapport ou non en fonction de vos besoins.

Matthieu M.
la source
1
Cette. Les facteurs décisifs wrt. Sont fondamentalement hors de portée de C ++.
Martin Ba
4

Une exception que vous attrapez vous donne la possibilité d'imprimer un joli message d'erreur ou même d'essayer de récupérer de l'erreur (peut-être en relançant simplement l'application).

Cependant, en C ++, une exception ne contient pas d'informations sur l'état du programme lors de son lancement. Si vous l'attrapez, tout cet état est oublié, tandis que si vous laissez le programme planter, l'état est généralement toujours là et peut être lu à partir du vidage du programme, ce qui facilite le débogage.

C'est donc un compromis.

Sebastian Redl
la source
4

Au moment où vous savez que vous devez abandonner, allez-y et appelez std::terminatedéjà pour limiter tout dommage supplémentaire.

Si vous savez que vous pouvez vous détendre en toute sécurité, faites-le à la place. N'oubliez pas que le déroulement de la pile n'est pas garanti lorsqu'une exception n'est jamais interceptée, donc attrapez et relancez.

Si vous pouvez signaler / enregistrer l'erreur en toute sécurité mieux que le système ne le fera tout seul, allez-y.
Mais assurez-vous vraiment de ne pas aggraver les choses par inadvertance.

Il est souvent trop tard pour enregistrer des données lorsque vous détectez une erreur irrécupérable, bien que cela dépende de l'erreur spécifique.
Quoi qu'il en soit, si votre programme est écrit pour une récupération rapide, le tuer peut être le meilleur moyen de le terminer, même s'il ne s'agit que d'un arrêt normal.

Déduplicateur
la source
1

Crasher gracieusement est une bonne chose la plupart du temps - mais il y a des compromis. Parfois, c'est une bonne chose de planter. Je dois mentionner que je pense principalement au débogage dans le très grand. Pour un programme simple - bien que cela puisse encore être utile, il est loin d'être aussi utile qu'avec un programme très complexe (ou plusieurs programmes complexes interagissant).

Vous ne voulez pas planter en public (bien que ce soit vraiment inévitable - les programmes se bloquent, et un programme non-crash vraiment vérifiable mathématiquement n'est pas ce dont nous parlons ici). Pensez à Bill Gates BSODing au milieu d'une démo - mauvais, non? Néanmoins, nous pouvons essayer de comprendre pourquoi nous nous sommes crashés et ne plus planter de la même manière.

J'ai activé une fonctionnalité du rapport d'erreurs Windows qui crée des vidages sur incident locaux sur les exceptions non gérées. Cela fonctionne à merveille si vous avez les fichiers de symboles associés à votre build (en panne). Chose intéressante, parce que j'ai configuré cet outil, je veux planter davantage - car j'apprends de chaque crash. Ensuite, je peux corriger les bugs et planter moins.

Donc pour l'instant, je veux que mes programmes se lancent complètement et se bloquent. À l'avenir, je pourrais vouloir manger gracieusement toutes mes exceptions - mais pas si je peux les faire fonctionner pour moi.

Vous pouvez en savoir plus sur les vidages sur incident locaux ici si vous êtes intéressé: Collecte des vidages en mode utilisateur

PerryC
la source
-6

En fin de compte, si une exception se propage au-delà de main (), cela va planter votre application, et dans mon esprit, une application ne devrait jamais se bloquer. Si vous pouvez planter une application au même endroit, pourquoi pas n'importe où? Pourquoi se soucier du traitement des exceptions? (Sarcasme, ne suggérant pas vraiment cela ...)

Vous pouvez avoir un essai / capture global qui imprime un message élégant indiquant à l'utilisateur qu'une erreur irrécupérable s'est produite et qu'il doit envoyer un e-mail à Bob dans le service informatique à ce sujet ou quelque chose de similaire, mais le plantage est complètement non professionnel et inacceptable. Il est trivial d'empêcher et vous pouvez informer un utilisateur de ce qui vient de se passer et comment réparer les choses pour que cela ne se reproduise plus.

Le crash est une heure strictement amateur.

Roger Hill
la source
8
Donc, mieux vaut prétendre que tout fonctionne correctement et remplacer tout le stockage permanent par du charabia à la place?
Déduplicateur
1
@Deduplicator: Non: vous pouvez toujours intercepter l'exception et la gérer.
Giorgio
5
Si vous savez comment le gérer, vous le ferez probablement bien avant de vous lever main. Si vous ne savez pas comment le gérer, faire semblant de le faire est évidemment un bug vraiment horrible.
Déduplicateur
8
mais planter n'est pas du tout professionnel et inacceptable => passer du temps à travailler sur des fonctionnalités inutiles n'est pas professionnel: planter n'a d'importance que si cela compte pour l'utilisateur final, si ce n'est pas le cas, le laisser se planter (et obtenir un rapport de plantage, avec un vidage de la mémoire) est plus économique.
Matthieu M.
Matthieu M. si cela vous prend vraiment autant d'efforts pour enregistrer une erreur, enregistrer tous les fichiers ouverts et peindre un message amical à l'écran, je ne sais pas quoi dire. Si vous pensez qu'il est inutile d'échouer gracieusement, vous devriez probablement aller parler à celui qui fait du support technique pour votre code.
Roger Hill