Wie wird die Laufzeit der C ++ - Ausnahmebehandlung implementiert?

82

Ich bin fasziniert davon, wie der C ++ - Ausnahmebehandlungsmechanismus funktioniert. Wo wird das Ausnahmeobjekt gespeichert und wie breitet es sich über mehrere Bereiche aus, bis es abgefangen wird? Wird es in einem globalen Bereich gespeichert?

Da dies compilerspezifisch sein könnte, könnte jemand dies im Kontext der g ++ - Compilersuite erklären?

Paul Joseph
quelle
4
Lesen Sie diesen Artikel wird Ihnen helfen
Ahmed Said
Ich weiß es nicht - aber ich vermute, dass die C ++ - Spezifikation eine klare Definition hat. (Ich kann mich jedoch irren)
Paul Nathan
2
Nein, die Spezifikation gibt keine Definition. Es diktiert das Verhalten, nicht die Implementierung. Paul, vielleicht möchten Sie angeben, an welcher Implementierung Sie interessiert sind.
Rob Kennedy
1
Verwandte Frage: stackoverflow.com/questions/307610/…
CesarB
Siehe Abschnitt 5.4 des Technischen Berichts über C ++
Yogesh Arora

Antworten:

48

Die Implementierungen können abweichen, aber es gibt einige grundlegende Ideen, die sich aus den Anforderungen ergeben.

Das Ausnahmeobjekt selbst ist ein Objekt, das in einer Funktion erstellt und in einem Aufrufer davon zerstört wurde. Daher ist es normalerweise nicht möglich, das Objekt auf dem Stapel zu erstellen. Andererseits sind viele Ausnahmeobjekte nicht sehr groß. Ergo kann man zB einen 32-Byte-Puffer und einen Überlauf zum Heap erstellen, wenn tatsächlich ein größeres Ausnahmeobjekt benötigt wird.

Für die tatsächliche Übertragung der Kontrolle gibt es zwei Strategien. Eine besteht darin, genügend Informationen im Stapel selbst aufzuzeichnen, um den Stapel abzuwickeln. Dies ist im Grunde eine Liste der auszuführenden Destruktoren und Ausnahmebehandlungsroutinen, die die Ausnahme möglicherweise abfangen. Wenn eine Ausnahme auftritt, führen Sie den Stapel aus, der diese Destruktoren ausführt, bis Sie einen passenden Fang finden.

Die zweite Strategie verschiebt diese Informationen in Tabellen außerhalb des Stapels. Wenn nun eine Ausnahme auftritt, wird der Aufrufstapel verwendet, um herauszufinden, welche Bereiche eingegeben, aber nicht beendet werden. Diese werden dann in den statischen Tabellen nachgeschlagen, um festzustellen, wo die ausgelöste Ausnahme behandelt wird und welche Destruktoren dazwischen ausgeführt werden. Dies bedeutet, dass auf dem Stapel weniger Ausnahmekosten anfallen. Absenderadressen werden sowieso benötigt. Die Tabellen sind zusätzliche Daten, aber der Compiler kann sie in ein bedarfsgesteuertes Segment des Programms einfügen.

MSalters
quelle
4
AFAIR g ++ verwendet den zweiten Adressentabellenansatz, vermutlich aus Gründen der Kompatibilität mit C. Der Microsoft C ++ - Compiler verwendet einen kombinierten Ansatz, da seine C ++ - Ausnahmen auf SEH (strukturierte Ausnahmebehandlung) basieren. In jeder C ++ - Funktion erstellt und registriert MSC ++ einen SEH-Ausnahmebehandlungsdatensatz, der auf eine Tabelle mit Adressbereichen für Try-Catch-Blöcke und Destruktoren in dieser bestimmten Funktion verweist. Wenn Pakete eine C ++ - Ausnahme als SEH-Ausnahme auslösen und RaiseException () aufrufen, gibt SEH die Steuerung an die C ++ - spezifische Handlerroutine zurück.
Anton Tykhyy
1
@Anton: Ja, es wird der Adressentabellenansatz verwendet. Weitere Informationen finden Sie in meiner Antwort auf eine andere Frage unter stackoverflow.com/questions/307610/… .
CesarB
Danke für die Antwort. Sie können sehen, wie C-Puristen Angst vor C ++ und seinen Ausnahmen haben könnten. Die Idee, dass ein einfacher Versuch / Fang zur Laufzeit unabsichtlich eine Reihe von Stapelobjekten erstellen oder Ihr Programm mit zusätzlichen Tabellen aufblähen kann, ist der Grund, warum eingebettete Systeme diese häufig vermeiden.
Speedplane
@speedplane: Nein, das liegt eher an mangelndem Verständnis. Die Fehlerbehandlung ist niemals kostenlos. C zwingt Sie nur, es selbst zu schreiben. Und wir alle wissen, wie vielen C-Programmen in einem selten verwendeten Codepfad ein free()oder ein fehlt fclose().
MSalters
@ MSalters Ich bin nicht anderer Meinung, es ist fast völlig ein Mangel an Verständnis. Ingenieure verstehen oft nicht, wie Ausnahmen funktionieren und wie sich Ausnahmen auf ihren Code auswirken, was dann zu Recht zu Zögern bei der Verwendung von Ausnahmen führt. Wenn die Implementierung der Ausnahmebehandlung klarer kommuniziert würde (und nicht magisch erscheinen würde), würden viele weniger zögern, sie zu verwenden.
Speedplane
20

Dies ist in 15.1 Auslösen einer Ausnahme vom Standard definiert.

Der Wurf erzeugt ein temporäres Objekt.
Wie der Speicher für dieses temporäre Objekt zugewiesen wird, ist nicht angegeben.

Nach dem Erstellen des temporären Objekts wird die Steuerung an den nächstgelegenen Handler im Aufrufstapel übergeben. Abwickeln des Stapels zwischen Wurf- und Fangpunkt. Während des Abwickelns des Stapels werden alle Stapelvariablen in umgekehrter Reihenfolge der Erstellung zerstört.

Sofern die Ausnahme nicht erneut ausgelöst wird, wird die temporäre Ausnahme am Ende des Handlers zerstört, an dem sie abgefangen wurde.

Hinweis: Wenn Sie nach Referenz abfangen, bezieht sich die Referenz auf das temporäre Objekt. Wenn Sie nach Wert abfangen, wird das temporäre Objekt in den Wert kopiert (und erfordert daher einen Kopierkonstruktor).

Ratschläge von S.Meyers (Catch by const reference).

try
{
    // do stuff
}
catch(MyException const& x)
{
}
catch(std::exception const& x)
{
}
Martin York
quelle
3
Etwas anderes, das nicht spezifiziert ist, ist, wie das Programm den Stapel abwickelt und wie das Programm weiß, wo sich der "nächste Handler" befindet. Ich bin mir ziemlich sicher, dass Borland ein Patent auf eine Methode zur Implementierung besitzt.
Rob Kennedy
Solange die Objekte in umgekehrter Reihenfolge der Erstellung zerstört werden, sind die Implementierungsdetails nur wichtig, wenn Sie ein Compiler-Ingenieur sind.
Martin York
1
Abgestimmt: a) "Scott Meyers", nicht "S. Myers"; b) unwahres Zitat: "Effektives C ++": "Punkt 13: Ausnahmen durch Bezugnahme abfangen. " Dies ermöglicht das Optimieren / Anhängen von Informationen an das Ausnahmeobjekt.
Sebastian Mach
3
@phresnel: Vergessen Sie nicht Punkt 21: "Verwenden Sie const wann immer möglich". Es gibt keinen guten Grund, eine Ausnahme zu optimieren. Sie sollten a) "reparieren und verwerfen", b) erneut werfen oder c) eine neue Ausnahme generieren.
Martin York
1
@phresnel: Ja, Sie haben Ihre Gründe (stimmen nicht mit Ihrer Logik überein), ich habe meine und obwohl ich nicht behaupten werde, mit ihnen über dieses spezielle Thema gesprochen zu haben oder tatsächlich ihre Gedanken zu kennen (Meyers, Alexandrescu und Sutter), glaube ich Meine Interpretation gilt. Wenn Sie sich jedoch in der Gegend von Seattle befinden, können Sie mit allen dreien sprechen, da sie regelmäßig an der North West C ++ User Group teilnehmen (Meyers seltener als die anderen).
Martin York
13

Sie könnten einen Blick hier für eine ausführliche Erklärung.

Es kann auch hilfreich sein, einen Trick zu betrachten, der in C verwendet wird, um eine grundlegende Art der Ausnahmebehandlung zu implementieren. Dies beinhaltet die Verwendung von setjmp () und longjmp () auf folgende Weise: Ersteres speichert den Stapel, um den Ausnahmebehandler zu markieren (wie "catch"), während letzteres verwendet wird, um einen Wert zu "werfen". Der "geworfene" Wert wird so gesehen, als ob er von einer aufgerufenen Funktion zurückgegeben wurde. Der "try block" endet, wenn setjmp () erneut aufgerufen wird oder wenn die Funktion zurückkehrt.

Eduard - Gabriel Munteanu
quelle