Qu'est-ce que le dépilage de pile? J'ai cherché, mais je n'ai pas trouvé de réponse éclairante!
Le dénouement des piles est généralement évoqué dans le cadre de la gestion des exceptions. Voici un exemple:
void func( int x )
{
char* pleak = new char[1024]; // might be lost => memory leak
std::string s( "hello world" ); // will be properly destructed
if ( x ) throw std::runtime_error( "boom" );
delete [] pleak; // will only get here if x == 0. if x!=0, throw exception
}
int main()
{
try
{
func( 10 );
}
catch ( const std::exception& e )
{
return 1;
}
return 0;
}
Ici, la mémoire allouée pour pleak
sera perdue si une exception est levée, alors que la mémoire allouée à s
sera libérée correctement par std::string
destructeur en tout cas. Les objets alloués sur la pile sont "déroulés" à la sortie de la portée (ici la portée est de la fonction func
.). Ceci est effectué par le compilateur en insérant des appels aux destructeurs de variables automatiques (de pile).
Maintenant, ceci est un concept très puissant menant à la technique appelée RAII , c'est-à-dire L'initialisation est une initialisation , qui nous aide à gérer des ressources telles que la mémoire, les connexions à la base de données, les descripteurs de fichiers ouverts, etc. en C++.
Maintenant, cela nous permet de fournir garanties de sécurité exceptionnelles .
Tout cela concerne le C++:
Définition : Lorsque vous créez des objets de manière statique (sur la pile au lieu de les allouer dans la mémoire de tas) et effectuez des appels de fonction, ils sont "empilés". .
Quand une étendue (tout ce qui est délimité par {
Et }
) Est quittée (en utilisant return XXX;
, En atteignant la fin de l'étendue ou en lançant une exception), tout ce qui s'y trouve est détruit (les destructeurs sont appelés pour tout). Ce processus de destruction d'objets locaux et d'appels de destructeurs est appelé "déroulement de pile" .
Vous avez les problèmes suivants liés au déroulement de la pile:
éviter les fuites de mémoire (tout ce qui est alloué dynamiquement et qui n'est pas géré par un objet local et nettoyé dans le destructeur sera filtré) - voir RAII référé par Nikolai, et la documentation d'accélération: : scoped_ptr ou cet exemple d'utilisation de boost :: mutex :: scoped_lock .
cohérence du programme: les spécifications C++ indiquent qu'il ne faut jamais lever une exception avant que toute exception existante ait été gérée. Cela signifie que le processus de déroulement de la pile ne doit jamais lever une exception (utilisez uniquement du code garantissant de ne pas lancer de destructeurs, ou entourez tout dans des destructeurs avec try {
Et } catch(...) {}
).
Si un destructeur lève une exception pendant le dépilage de pile, vous vous retrouvez dans le pays au comportement indéfini, ce qui pourrait entraîner la fin inattendue de votre programme (comportement le plus courant) ou la fin de l'univers (théoriquement possible mais pas encore été observé dans la pratique).
De manière générale, une pile "dérouler" est à peu près synonyme de la fin d'un appel de fonction et de son éclatement ultérieur.
Cependant, en particulier dans le cas de C++, le déroulement de la pile est lié à la façon dont C++ appelle les destructeurs pour les objets alloués depuis le début de tout bloc de code. Les objets créés dans le bloc sont désalloués dans l'ordre inverse de leur affectation.
Le dépilage de pile est un concept principalement C++, traitant de la manière dont les objets alloués à la pile sont détruits lors de la sortie de son étendue (normalement ou par le biais d'une exception).
Disons que vous avez ce fragment de code:
void hw() {
string hello("Hello, ");
string world("world!\n");
cout << hello << world;
} // at this point, "world" is destroyed, followed by "hello"
Je ne sais pas si vous avez déjà lu ceci, mais l'article de Wikipedia sur la pile d'appels a une explication décente.
Dénouement:
Si vous revenez de la fonction appelée, le cadre supérieur apparaîtra en dehors de la pile, laissant peut-être une valeur de retour. L’acte plus général de supprimer une ou plusieurs images de la pile pour reprendre l’exécution ailleurs dans le programme est appelé un déroulement de la pile et doit être effectué lorsque non local des structures de contrôle sont utilisées, telles que celles utilisées pour la gestion des exceptions. Dans ce cas, le cadre de pile d'une fonction contient une ou plusieurs entrées spécifiant des gestionnaires d'exceptions. Lorsqu’une exception est levée, la pile est déroulée jusqu’à ce qu’on trouve un gestionnaire prêt à gérer (attraper) le type de l’exception levée.
Certaines langues ont d'autres structures de contrôle qui nécessitent un déroulement général. Pascal permet à une instruction globale goto de transférer le contrôle à partir d'une fonction imbriquée vers une fonction externe précédemment appelée. Cette opération nécessite que la pile soit déroulée en supprimant autant de trames de pile que nécessaire pour restaurer le contexte approprié pour transférer le contrôle à l'instruction cible dans la fonction externe englobante. De même, C a les fonctions setjmp et longjmp qui agissent comme des gotos non locales. Common LISP permet de contrôler ce qui se passe lorsque la pile est déroulée en utilisant l'opérateur spécial de protection contre le déroulement.
Lorsque vous appliquez une continuation, la pile est (logiquement) déroulée et ensuite rembobinée avec la pile de la continuation. Ce n'est pas le seul moyen d'implémenter des suites. par exemple, en utilisant plusieurs piles explicites, l'application d'une continuation peut simplement activer sa pile et générer une valeur à transmettre. Le langage de programmation Scheme permet à des thunks arbitraires d'être exécutés à des points spécifiés lors du "déroulement" ou du "rembobinage" de la pile de contrôle lorsqu'une continuation est invoquée.
Inspection [modifier]
J'ai lu un article de blog qui m'a aidé à comprendre.
Qu'est-ce que le déroulement de pile?
Dans toutes les langues qui prennent en charge les fonctions récursives (c'est-à-dire à peu près tout sauf Fortran 77 et Brainf * ck), le langage d'exécution conserve une pile des fonctions en cours d'exécution. Le dépilage de pile est un moyen d’inspecter et éventuellement de modifier cette pile.
Pourquoi voudriez-vous faire cela?
La réponse peut sembler évidente, mais il existe plusieurs situations liées, mais légèrement différentes, dans lesquelles le déroulement est utile ou nécessaire:
- En tant que mécanisme de flux de contrôle d'exécution (exceptions C++, C longjmp (), etc.).
- Dans un débogueur, pour montrer à l'utilisateur la pile.
- Dans un profileur, prélever un échantillon de la pile.
- Depuis le programme lui-même (comme avec un gestionnaire d'accidents pour montrer la pile).
Celles-ci ont des exigences légèrement différentes. Certaines sont critiques pour la performance, d'autres non. Certains nécessitent la capacité de reconstruire des registres à partir d'une trame externe, d'autres non. Mais nous allons entrer dans tout cela dans une seconde.
Vous pouvez trouver le post complet ici .
Tout le monde a parlé de la gestion des exceptions en C++. Mais, je pense qu’il existe une autre connotation pour le déroulement de la pile et qui est liée au débogage. Un débogueur doit effectuer un déroulement de pile chaque fois qu'il est supposé aller à une image antérieure à l'image actuelle. Cependant, il s’agit d’une sorte de déroulement virtuel car il faut revenir en arrière pour revenir au cadre actuel. L'exemple de ceci pourrait être les commandes up/down/bt dans gdb.
IMO, le diagramme ci-dessous donné dans cet article explique admirablement l'effet du déroulement de la pile sur le chemin de l'instruction suivante (à exécuter une fois qu'une exception est levée et non capturée):
Dans la photo:
Dans le second cas, lorsqu'une exception se produit, la pile d'appels de fonctions est recherchée linéairement pour le gestionnaire d'exceptions. La recherche se termine à la fonction avec le gestionnaire d'exceptions, à savoir main()
avec un bloc try-catch
Englobant, mais pas avant en supprimant tout les entrées qui le précèdent de la pile d’appel de fonction.
Le runtime C++ détruit toutes les variables automatiques créées entre throw et catch. Dans cet exemple simple ci-dessous, les lancers f1 () et les captures main (), entre les objets de type B et A, sont créés sur la pile dans cet ordre. Lorsque f1 () lance, les destructeurs de B et A sont appelés.
#include <iostream>
using namespace std;
class A
{
public:
~A() { cout << "A's dtor" << endl; }
};
class B
{
public:
~B() { cout << "B's dtor" << endl; }
};
void f1()
{
B b;
throw (100);
}
void f()
{
A a;
f1();
}
int main()
{
try
{
f();
}
catch (int num)
{
cout << "Caught exception: " << num << endl;
}
return 0;
}
Le résultat de ce programme sera
B's dtor
A's dtor
En effet, le callstack du programme lorsque f1 () ressemble à
f1()
f()
main()
Ainsi, lorsque f1 () est affiché, la variable automatique b est détruite, puis lorsque f() est affichée, la variable automatique a est détruite.
J'espère que cela aide, codage heureux!
Lorsqu'une exception est levée et que le contrôle passe d'un bloc try à un gestionnaire, le moteur d'exécution C++ appelle des destructeurs pour tous les objets automatiques construits depuis le début du bloc try. Ce processus s'appelle le déroulement de la pile. Les objets automatiques sont détruits dans l'ordre inverse de leur construction. (Les objets automatiques sont des objets locaux déclarés auto ou register, ou non déclarés statiques ou externes. Un objet automatique x est supprimé chaque fois que le programme quitte le bloc dans lequel x est déclaré.)
Si une exception est levée lors de la construction d'un objet composé de sous-objets ou d'éléments de tableau, les destructeurs ne sont appelés que pour les sous-objets ou éléments de tableau construits avec succès avant la levée de l'exception. Un destructeur pour un objet statique local ne sera appelé que si l'objet a été construit avec succès.
Dans Java empiler ou dérouler une pile n'est pas très important (avec le ramasse-miettes). Dans de nombreux documents sur la gestion des exceptions, j'ai vu ce concept (décompression de la pile). ou C++. avec try catch
blocs que nous ne devrions pas oublier: pile libre de tous les objets après les blocs locaux.