Was ist das Abwickeln des Stapels? Durchsucht, aber keine aufschlussreiche Antwort gefunden!
Was ist das Abwickeln des Stapels? Durchsucht, aber keine aufschlussreiche Antwort gefunden!
Antworten:
Das Abwickeln von Stapeln wird normalerweise im Zusammenhang mit der Ausnahmebehandlung erwähnt. Hier ist ein Beispiel:
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;
}
Hier Speicher für zugewiesen pleak
verloren, wenn eine Ausnahme ausgelöst wird, während der zugewiesene Speicher in jedem Fall s
vom std::string
Destruktor ordnungsgemäß freigegeben wird. Die auf dem Stapel zugewiesenen Objekte werden beim Verlassen des Bereichs "abgewickelt" (hier ist der Bereich von der Funktion func
). Dies erfolgt durch den Compiler, der Aufrufe an Destruktoren von automatischen (Stapel-) Variablen einfügt.
Dies ist ein sehr leistungsfähiges Konzept, das zur Technik RAII führt Ressourcenerfassung ist Initialisierung , mit der wir Ressourcen wie Speicher, Datenbankverbindungen, offene Dateideskriptoren usw. in C ++ verwalten können.
Dies ermöglicht es uns nun, Ausnahmesicherheitsgarantien zu geben .
delete [] pleak;
wird nur erreicht, wenn x == 0.
All dies bezieht sich auf C ++:
Definition : Wenn Sie Objekte statisch erstellen (auf dem Stapel, anstatt sie im Heapspeicher zuzuweisen) und Funktionsaufrufe ausführen, werden sie "gestapelt".
Wenn ein Bereich (alles, was durch {
und begrenzt ist }
) beendet wird (indem Sie verwenden return XXX;
, das Ende des Bereichs erreichen oder eine Ausnahme auslösen), wird alles in diesem Bereich zerstört (Destruktoren werden für alles aufgerufen). Dieser Vorgang des Zerstörens lokaler Objekte und des Aufrufs von Destruktoren wird als Abwickeln des Stapels bezeichnet.
Sie haben die folgenden Probleme beim Abwickeln des Stapels:
Vermeiden von Speicherlecks (alles, was dynamisch zugewiesen wird und nicht von einem lokalen Objekt verwaltet und im Destruktor bereinigt wird, geht verloren) - siehe RAII, auf das Nikolai verweist , und die Dokumentation zu boost :: scoped_ptr oder dieses Beispiel für die Verwendung von boost :: mutex :: scoped_lock .
Programmkonsistenz: Die C ++ - Spezifikationen besagen, dass Sie niemals eine Ausnahme auslösen sollten, bevor eine vorhandene Ausnahme behandelt wurde. Dies bedeutet, dass der Stapelabwicklungsprozess niemals eine Ausnahme auslösen sollte (entweder nur Code verwenden, der garantiert keine Destruktoren einwirft, oder alles in Destruktoren mit try {
und umgeben } catch(...) {}
).
Wenn ein Destruktor beim Abwickeln des Stapels eine Ausnahme auslöst, befinden Sie sich im Land des undefinierten Verhaltens, das dazu führen kann, dass Ihr Programm unerwartet beendet wird (häufigstes Verhalten) oder das Universum endet (theoretisch möglich, in der Praxis jedoch noch nicht beobachtet).
Im Allgemeinen ist ein "Abwickeln" eines Stapels so ziemlich gleichbedeutend mit dem Ende eines Funktionsaufrufs und dem anschließenden Aufspringen des Stapels.
Insbesondere im Fall von C ++ hat das Abwickeln des Stapels jedoch damit zu tun, wie C ++ die Destruktoren für die Objekte aufruft, die seit dem Start eines Codeblocks zugewiesen wurden. Objekte, die innerhalb des Blocks erstellt wurden, werden in umgekehrter Reihenfolge ihrer Zuordnung freigegeben.
try
Blöcke haben nichts Besonderes . Stapelobjekte, die in einem Block zugewiesen sind (unabhängig davon, ob sie vorhanden sind try
oder nicht), werden beim Beenden des Blocks abgewickelt.
Das Abwickeln von Stapeln ist hauptsächlich ein C ++ - Konzept, das sich mit der Zerstörung von Objekten mit Stapelzuweisung befasst, wenn der Gültigkeitsbereich beendet wird (entweder normal oder durch eine Ausnahme).
Angenommen, Sie haben dieses Codefragment:
void hw() {
string hello("Hello, ");
string world("world!\n");
cout << hello << world;
} // at this point, "world" is destroyed, followed by "hello"
Ich weiß noch nicht, ob du das gelesen hast, aber der Artikel von Wikipedia über den Aufrufstapel enthält eine anständige Erklärung.
Abwickeln:
Wenn Sie von der aufgerufenen Funktion zurückkehren, wird der obere Frame vom Stapel entfernt und möglicherweise ein Rückgabewert hinterlassen. Der allgemeinere Vorgang, einen oder mehrere Frames vom Stapel zu entfernen, um die Ausführung an anderer Stelle im Programm fortzusetzen, wird aufgerufen Abwickeln des Stapels bezeichnet und muss ausgeführt werden, wenn nicht lokale Steuerstrukturen verwendet werden, wie sie beispielsweise für die Ausnahmebehandlung verwendet werden. In diesem Fall enthält der Stapelrahmen einer Funktion einen oder mehrere Einträge, die Ausnahmebehandlungsroutinen angeben. Wenn eine Ausnahme ausgelöst wird, wird der Stapel abgewickelt, bis ein Handler gefunden wird, der bereit ist, den Typ der ausgelösten Ausnahme zu behandeln (abzufangen).
Einige Sprachen haben andere Kontrollstrukturen, die ein allgemeines Abwickeln erfordern. Mit Pascal kann eine globale goto-Anweisung die Kontrolle aus einer verschachtelten Funktion in eine zuvor aufgerufene äußere Funktion übertragen. Diese Operation erfordert, dass der Stapel abgewickelt wird, wobei so viele Stapelrahmen wie nötig entfernt werden, um den richtigen Kontext wiederherzustellen und die Kontrolle an die Zielanweisung innerhalb der umschließenden äußeren Funktion zu übertragen. In ähnlicher Weise verfügt C über die Funktionen setjmp und longjmp, die als nicht lokale gotos fungieren. Common Lisp ermöglicht die Steuerung dessen, was passiert, wenn der Stapel abgewickelt wird, indem der spezielle Bediener zum Abwickeln verwendet wird.
Beim Anwenden einer Fortsetzung wird der Stapel (logisch) abgewickelt und dann mit dem Stapel der Fortsetzung zurückgespult. Dies ist nicht die einzige Möglichkeit, Fortsetzungen zu implementieren. Wenn Sie beispielsweise mehrere explizite Stapel verwenden, kann die Anwendung einer Fortsetzung einfach ihren Stapel aktivieren und einen zu übergebenden Wert aufwickeln. Die Programmiersprache Scheme ermöglicht die Ausführung beliebiger Thunks an bestimmten Punkten beim "Abwickeln" oder "Zurückspulen" des Steuerungsstapels, wenn eine Fortsetzung aufgerufen wird.
Inspektion [Bearbeiten]
Ich habe einen Blog-Beitrag gelesen, der mir das Verständnis erleichtert hat.
Was ist das Abwickeln des Stapels?
In jeder Sprache, die rekursive Funktionen unterstützt (dh so ziemlich alles außer Fortran 77 und Brainf * ck), enthält die Sprachlaufzeit einen Stapel der Funktionen, die gerade ausgeführt werden. Das Abwickeln des Stapels ist eine Möglichkeit, diesen Stapel zu überprüfen und möglicherweise zu ändern.
Warum willst du das tun?
Die Antwort mag offensichtlich erscheinen, aber es gibt mehrere verwandte, aber subtil unterschiedliche Situationen, in denen das Abwickeln nützlich oder notwendig ist:
- Als Laufzeit-Kontrollflussmechanismus (C ++ - Ausnahmen, C longjmp () usw.).
- In einem Debugger, um dem Benutzer den Stapel anzuzeigen.
- In einem Profiler eine Stichprobe des Stapels entnehmen.
- Aus dem Programm selbst (wie von einem Crash-Handler, um den Stack anzuzeigen).
Diese haben subtil unterschiedliche Anforderungen. Einige davon sind leistungskritisch, andere nicht. Einige erfordern die Fähigkeit, Register aus dem äußeren Rahmen zu rekonstruieren, andere nicht. Aber wir werden gleich darauf eingehen.
Den vollständigen Beitrag finden Sie hier .
Alle haben über die Ausnahmebehandlung in C ++ gesprochen. Aber ich denke, es gibt eine andere Konnotation für das Abwickeln von Stapeln, die mit dem Debuggen zusammenhängt. Ein Debugger muss das Abwickeln des Stapels durchführen, wenn er zu einem Frame vor dem aktuellen Frame wechseln soll. Dies ist jedoch eine Art virtuelles Abwickeln, da es zurückgespult werden muss, wenn es zum aktuellen Frame zurückkehrt. Das Beispiel hierfür könnten up / down / bt-Befehle in gdb sein.
IMO, das unten stehende Diagramm in diesem Artikel erklärt auf wunderbare Weise die Auswirkung des Abwickelns des Stapels auf die Route des nächsten Befehls (wird ausgeführt, sobald eine Ausnahme ausgelöst wird, die nicht erfasst wurde):
Auf dem Bild:
Im zweiten Fall, wenn eine Ausnahme auftritt, wird der Funktionsaufrufstapel linear nach dem Ausnahmebehandler durchsucht. Die Suche endet bei der Funktion mit Ausnahmebehandlungsroutine, dh main()
mit umschließendem try-catch
Block, jedoch nicht vor dem Entfernen aller vorhergehenden Einträge aus dem Funktionsaufrufstapel.
Die C ++ - Laufzeit zerstört alle automatischen Variablen, die zwischen throw & catch erstellt wurden. In diesem einfachen Beispiel unten werden f1 () -Würfe und main () -Fänge zwischen Objekten des Typs B und A in dieser Reihenfolge auf dem Stapel erstellt. Wenn f1 () wirft, werden die Destruktoren von B und A aufgerufen.
#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;
}
Die Ausgabe dieses Programms wird sein
B's dtor
A's dtor
Dies liegt daran, dass der Aufruf des Programms beim Auslösen von f1 () so aussieht
f1()
f()
main()
Wenn also f1 () gepoppt wird, wird die automatische Variable b zerstört, und wenn f () gepoppt wird, wird die automatische Variable a zerstört.
Hoffe das hilft, viel Spaß beim Codieren!
Wenn eine Ausnahme ausgelöst wird und die Steuerung von einem try-Block an einen Handler übergeben wird, ruft die C ++ - Laufzeit Destruktoren für alle automatischen Objekte auf, die seit Beginn des try-Blocks erstellt wurden. Dieser Vorgang wird als Abwickeln des Stapels bezeichnet. Die automatischen Objekte werden in umgekehrter Reihenfolge ihrer Konstruktion zerstört. (Automatische Objekte sind lokale Objekte, die als automatisch oder registriert deklariert oder nicht als statisch oder extern deklariert wurden. Ein automatisches Objekt x wird gelöscht, wenn das Programm den Block verlässt, in dem x deklariert ist.)
Wenn während der Erstellung eines Objekts, das aus Unterobjekten oder Array-Elementen besteht, eine Ausnahme ausgelöst wird, werden Destruktoren nur für die Unterobjekte oder Array-Elemente aufgerufen, die erfolgreich erstellt wurden, bevor die Ausnahme ausgelöst wurde. Ein Destruktor für ein lokales statisches Objekt wird nur aufgerufen, wenn das Objekt erfolgreich erstellt wurde.
Im Java-Stack ist Unwiding oder Unwounding nicht sehr wichtig (mit Garbage Collector). In vielen Ausnahmebehandlungspapieren habe ich dieses Konzept gesehen (Stack Unwinding), insbesondere befassen sich diese Writer mit der Ausnahmebehandlung in C oder C ++. Bei try catch
Blöcken sollten wir nicht vergessen: Freier Stapel von allen Objekten nach lokalen Blöcken .