web-dev-qa-db-fra.com

Call-stack pour les exceptions en C++

Aujourd'hui, dans mon code multiplate-forme C++, je peux essayer toutes les fonctions. Dans chaque bloc catch, j'ajoute le nom de la fonction actuelle à l'exception et la renvoie à nouveau, de sorte que, dans le bloc catch le plus élevé (où j'imprime enfin les détails de l'exception), j'ai la pile d'appels complète, ce qui m'aide à rechercher la cause de l'exception.

Est-ce une bonne pratique ou existe-t-il de meilleurs moyens d'obtenir la pile d'appels pour l'exception?

29
Igor Oks

Non, c'est profondément horrible et je ne vois pas pourquoi vous avez besoin d'une pile d'appels dans l'exception elle-même. Je trouve le motif de l'exception, le numéro de ligne et le nom du fichier du code où l'exception initiale s'est produite est suffisant. 

Cela dit, si vous devez réellement disposer d’une trace de pile, vous devez générer les informations de pile d’appel UNE FOIS sur le site de lancement de l’exception. Il n’existe pas de méthode portable simple, mais utiliser quelque chose comme http://stacktrace.sourceforge.net/ combiné avec une bibliothèque similaire pour VC++ ne devrait pas être trop difficile.

24
anon

Ce que vous faites n'est pas une bonne pratique. Voici pourquoi:

1. C'est inutile.
Si vous compilez votre projet en mode débogage afin que les informations de débogage soient générées, vous pouvez facilement obtenir des traces pour la gestion des exceptions dans un débogueur tel que GDB.

2. C'est lourd.
C’est quelque chose que vous devez vous rappeler d’ajouter à chaque fonction. Si vous ratez une fonction, vous risquez de créer une grande confusion, surtout si c’est la fonction qui a provoqué l’exception. Et toute personne regardant votre code devra se rendre compte de ce que vous faites. De plus, je parie que vous avez utilisé quelque chose comme __FUNC__ ou __FUNCTION__ ou __PRETTY_FUNCTION__, qui, malheureusement, sont tous non standard (il n'y a pas de méthode standard en C++ pour obtenir le nom de la fonction).

3. C'est lent.
La propagation des exceptions en C++ est déjà assez lente, et l'ajout de cette logique ne fera que ralentir le codepath. Ce n'est pas un problème si vous utilisez des macros pour capturer et rediffuser, où vous pouvez facilement éviter les captures et rediffuser dans les versions de votre code. Sinon, la performance pourrait être un problème.

Bonnes pratiques
Bien qu'il ne soit pas recommandé de capturer et de rediffuser dans chaque fonction pour créer une trace de pile, il est conseillé de joindre le nom du fichier, le numéro de ligne et le nom de la fonction à l'origine de l'exception. jeté. Si vous utilisez boost :: exception avec BOOST_THROW_EXCEPTION, vous obtiendrez ce comportement gratuitement. Il est également bon de joindre à votre exception des informations explicatives qui faciliteront le débogage et la gestion de l'exception. Cela dit, tout cela devrait se produire au moment où l’exception est construite; une fois qu'il est construit, il devrait être autorisé à se propager à son gestionnaire ... vous ne devez pas prendre et rediffuser de manière répétée plus que strictement nécessaire. Si vous avez besoin de capturer et de rediffuser dans une fonction particulière pour attacher des informations cruciales, c'est très bien, mais attraper toutes les exceptions dans chaque fonction et pour attacher les informations déjà disponibles est tout simplement excessif.

21

Une solution plus intéressante consiste à créer une macro/classe Tracer. Donc, au sommet de chaque fonction, vous écrivez quelque chose comme:

TRACE()

et la macro ressemble à quelque chose comme:

Tracer t(__FUNCTION__);

et la classe Tracer ajoute le nom de la fonction à une pile globale lors de la construction et se supprime dès sa destruction. Ensuite, cette pile est toujours disponible pour la journalisation ou le débogage, la maintenance est beaucoup plus simple (une ligne) et elle n’entraîne pas de surcharge pour les exceptions.

Des exemples d'implémentations incluent des éléments tels que http://www.drdobbs.com/184405270 , http://www.codeproject.com/KB/cpp/cmtrace.aspx , et http://www.codeguru.com/cpp/vs/debug/tracing/article.php/c4429 . Les fonctions Linux comme ceci http://www.linuxjournal.com/article/6391 peuvent le faire de manière plus native, comme décrit dans cette question Stack Overflow: Comment générer un stacktrace lorsque mon application gcc C++ se bloque . ACE_Stack_Trace de ACE peut également être intéressant.

Quoi qu'il en soit, la méthode de traitement des exceptions est grossière, inflexible et coûteuse en calcul. Les solutions de construction de classe/macro sont beaucoup plus rapides et peuvent être compilées si nécessaire.

7
Scott Stafford

La solution à tous vos problèmes est un bon débogueur, généralement http://www.gnu.org/software/gdb/ sur linux ou Visual Studio sous Windows. Ils peuvent vous donner des traces de pile à la demande à tout moment du programme. 

Votre méthode actuelle est un véritable casse-tête de performance et de maintenance. Les débogueurs sont inventés pour atteindre votre objectif, mais sans les frais généraux.

2
Scott Stafford

Regardez cette SO Question . Cela pourrait être proche de ce que vous recherchez. Ce n'est pas multi-plateforme mais la réponse donne des solutions pour gcc et Visual Studio.

1
zooropa

Il y a un joli petit projet qui donne une jolie trace de pile:

https://github.com/bombela/backward-cpp

0
Scott Stafford

Une exception qui n'est pas gérée est laissée à la fonction appelante. Cela continue jusqu'à ce que l'exception soit gérée. Cela se produit avec ou sans try/catch autour d'un appel de fonction. En d'autres termes, si une fonction appelée n'appartenant pas à un bloc try, une exception qui survient dans cette fonction sera automatiquement transmise à la pile d'appels. Donc, tout ce que vous avez à faire est de placer la fonction la plus haute dans un bloc try et de gérer l'exception "..." dans le bloc catch. Cette exception intercepte toutes les exceptions. Ainsi, votre fonction la plus haut ressemblera à quelque chose comme

int main()
{
  try
  {
    top_most_func()
  }
  catch(...)
  {
    // handle all exceptions here
  }
}

Si vous souhaitez avoir des blocs de code spécifiques pour certaines exceptions, vous pouvez également le faire. Assurez-vous simplement que ceux-ci se produisent avant le bloc de capture d'exception "...".

0
zooropa

Un projet supplémentaire pour le support de trace de pile: ex_diag . Il n'y a pas de macros, la multiplateforme est présente, pas besoin de code énorme, l'outil est rapide, clair et facile à utiliser.

Ici, vous n’avez besoin que d’emballer des objets, qu’il faut tracer, et ils le seront si une exception se produit.

0
Boris

La liaison avec la bibliothèque libcsdbg (voir https://stackoverflow.com/a/18959030/364818 pour la réponse originale) ressemble à la méthode la plus propre pour obtenir une trace de pile sans modifier votre code source ou le code source tiers (c'est-à-dire STL).

Ceci utilise le compilateur pour instrumenter la collection de pile réelle, ce que vous voulez vraiment faire.

Je ne l'ai pas utilisé et il est terni par la GPL, mais cela semble être la bonne idée.

0
Mark Lakata