web-dev-qa-db-fra.com

Comment le runtime de gestion des exceptions C ++ est-il implémenté?

Je suis intrigué par le fonctionnement du mécanisme de gestion des exceptions C++. Plus précisément, où l'objet d'exception est-il stocké et comment se propage-t-il sur plusieurs étendues jusqu'à ce qu'il soit capturé? Est-il stocké dans une zone mondiale?

Puisque cela pourrait être spécifique au compilateur, quelqu'un pourrait-il l'expliquer dans le contexte de la suite de compilateurs g ++?

76
Paul Joseph

Les implémentations peuvent différer, mais certaines idées de base découlent des exigences.

L'objet d'exception lui-même est un objet créé dans une fonction, détruit dans un appelant de celle-ci. Par conséquent, il n'est généralement pas possible de créer l'objet sur la pile. En revanche, de nombreux objets d'exception ne sont pas très gros. Ergo, on peut créer par exemple un tampon de 32 octets et un débordement à amasser si un objet d'exception plus grand est réellement nécessaire.

Quant au transfert effectif de contrôle, deux stratégies existent. L'une consiste à enregistrer suffisamment d'informations dans la pile elle-même pour dérouler la pile. Il s'agit essentiellement d'une liste de destructeurs à exécuter et de gestionnaires d'exceptions pouvant intercepter l'exception. Lorsqu'une exception se produit, exécutez à nouveau la pile exécutant ces destructeurs jusqu'à ce que vous trouviez une capture correspondante.

La deuxième stratégie déplace ces informations dans des tables en dehors de la pile. Désormais, lorsqu'une exception se produit, la pile d'appels est utilisée pour déterminer les étendues entrées mais non fermées. Ceux-ci sont ensuite recherchés dans les tables statiques pour déterminer où l'exception levée sera gérée et quels destructeurs s'exécuter entre les deux. Cela signifie qu'il y a moins de surcharge d'exception sur la pile; les adresses de retour sont nécessaires de toute façon. Les tables sont des données supplémentaires, mais le compilateur peut les placer dans un segment du programme chargé à la demande.

43
MSalters

Ceci est défini dans 15.1 Lancer une exception à la norme.

Le lancer crée un objet temporaire.
La façon dont la mémoire de cet objet temporaire est allouée n'est pas spécifiée.

Après la création de l'objet temporaire, le contrôle est transmis au gestionnaire le plus proche de la pile d'appels. dérouler la pile entre le lancer et le point de capture. Lorsque la pile est déroulée, toutes les variables de la pile sont détruites dans l'ordre inverse de la création.

Sauf si l'exception est renvoyée, le temporaire est détruit à la fin du gestionnaire où il a été attrapé.

Remarque: Si vous capturez par référence, la référence fera référence au temporaire. Si vous capturez par valeur, l'objet temporaire est copié dans la valeur (et nécessite donc un constructeur de copie).

Conseils de S.Meyers (Capture par référence const).

try
{
    // do stuff
}
catch(MyException const& x)
{
}
catch(std::exception const& x)
{
}
18
Martin York

Vous pouvez jeter un oeil ici pour une explication détaillée.

Il peut également être utile de jeter un œil à une astuce utilisée en C simple pour implémenter une sorte de base de gestion des exceptions. Cela implique d'utiliser setjmp () et longjmp () de la manière suivante: le premier enregistre la pile afin de marquer le gestionnaire d'exceptions (comme "catch"), tandis que le second est utilisé pour "jeter" une valeur. La valeur "levée" est vue comme si elle avait été renvoyée par une fonction appelée. Le "bloc try" se termine lorsque setjmp () est appelé à nouveau ou lorsque la fonction revient.

12

Je sais que c'est une vieille question, mais il y a une très bonne exposition, expliquant à la fois les méthodes utilisées dans chacun de gcc et VC ici: http://www.hexblog.com /wp-content/uploads/2012/06/Recon-2012-Skochinsky-Compiler-Internals.pdf

8
Jules May