91 Stimmen

Wie wird das C++-Exception-Handling zur Laufzeit implementiert?

Ich bin fasziniert davon, wie der Mechanismus zur Ausnahmebehandlung in C++ funktioniert. Insbesondere interessiere ich mich dafür, wo das Ausnahmeobjekt gespeichert ist und wie es durch mehrere Bereiche propagiert wird, bis es abgefangen wird. Wird es in einem globalen Bereich gespeichert?

Weil dies compilerabhängig sein könnte, könnte jemand dies im Kontext des g++-Compilers erläutern?

5 Stimmen

Lesen Sie diesen Artikel wird Ihnen helfen.

0 Stimmen

Ich weiß es nicht - aber ich vermute, dass die C++ Spezifikation eine klare Definition hat. (Ich könnte aber auch falsch liegen)

2 Stimmen

Nein, die Spezifikation gibt keine Definition. Sie diktiert das Verhalten, nicht die Implementierung. Paul, du solltest angeben, an welcher Implementierung du interessiert bist.

57voto

MSalters Punkte 166675

Die Implementierungen können sich unterscheiden, 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 dem aufrufenden Funktionsaufruf zerstört wird. Daher ist es in der Regel nicht möglich, das Objekt auf dem Stapel zu erstellen. Andererseits sind viele Ausnahmeobjekte nicht sehr groß. Daher kann man z. B. einen 32-Byte-Puffer erstellen und bei Bedarf auf den Heap überlaufen, wenn ein größeres Ausnahmeobjekt tatsächlich benötigt wird.

Was die tatsächliche Kontrollübertragung betrifft, gibt es zwei Strategien. Eine besteht darin, ausreichend Informationen im Stapel selbst zu erfassen, um den Stapel aufzuheben. Dabei handelt es sich im Wesentlichen um eine Liste von Destruktoren, die ausgeführt werden sollen, und um Ausnahmebehandler, die die Ausnahme abfangen könnten. Wenn eine Ausnahme auftritt, wird der Stapel zurückverfolgt und die Destruktoren werden ausgeführt, bis ein passendes Catch gefunden wird.

Die zweite Strategie verschiebt diese Informationen in Tabellen außerhalb des Stapels. Wenn also eine Ausnahme auftritt, wird der Aufrufstapel verwendet, um festzustellen, in welche Gültigkeitsbereiche eingetreten, aber nicht verlassen wurden. Diese werden dann in den statischen Tabellen nachgeschlagen, um festzustellen, wo die geworfene Ausnahme behandelt werden soll und welche Destruktoren dazwischen ausgeführt werden sollen. Dies bedeutet, dass weniger Ausnahmeaufwand auf dem Stapel erfolgt; Rückkehradressen sind sowieso erforderlich. Die Tabellen sind zusätzliche Daten, aber der Compiler kann sie in einem nach Bedarf geladenen Segment des Programms platzieren.

6 Stimmen

AFAIR verwendet g++ den zweiten Ansatz mit der Adresstabelle, vermutlich aus Gründen der Kompatibilität mit C. Der Microsoft C++-Compiler verwendet einen kombinierten Ansatz, da seine C++-Ausnahmen auf SEH (Structured Exception Handling) aufbauen. 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 zeigt. throw verpackt eine C++-Ausnahme als SEH-Ausnahme und ruft RaiseException() auf, dann gibt SEH die Kontrolle an die C++-spezifische Handler-Routine zurück.

1 Stimmen

@Anton: Ja, es verwendet den Address-Table-Ansatz. Siehe meine Antwort auf eine andere Frage unter stackoverflow.com/questions/307610/… für die Details.

0 Stimmen

Vielen Dank für die Antwort. Sie können sehen, wie C-Puristen vor C++ und seinen Ausnahmen erschrecken könnten. Die Vorstellung, dass ein einfaches try/catch unbeabsichtigt eine Vielzahl von Stapelobjekten zur Laufzeit erstellen oder Ihr Programm mit zusätzlichen Tabellen aufblähen kann, ist der Grund, warum eingebettete Systeme sie oft vermeiden.

22voto

Martin York Punkte 245363

Dies ist in 15.1 des Standards definiert.

Das throw erzeugt ein temporäres Objekt.
Wie der Speicher für dieses temporäre Objekt allokiert wird, ist nicht spezifiziert.

Nach der Erstellung des temporären Objekts wird die Kontrolle an den nächsten Handler im Aufrufstapel übergeben. Dabei wird der Stapel zwischen throw und Fangpunkt abgewickelt. Beim Abwickeln des Stapels werden alle Stapelvariablen in umgekehrter Reihenfolge ihrer Erstellung zerstört.

Es sei denn, die Ausnahme wird erneut ausgelöst, wird das temporäre Objekt am Ende des Handlers, in dem es gefangen wurde, zerstört.

Hinweis: Wenn Sie per Referenz fangen, verweist die Referenz auf das temporäre Objekt. Wenn Sie per Wert fangen, wird das temporäre Objekt in den Wert kopiert (und erfordert daher einen Kopierkonstruktor).

Ratschlag von S. Meyers (Fangen Sie per const-Referenz).

try
{
    // etwas tun
}
catch(MyException const& x)
{
}
catch(std::exception const& x)
{
}

3 Stimmen

Etwas Anderes, das nicht spezifiziert ist, ist wie das Programm den Stapel entwirrt und wie das Programm weiß, wo der "nächste Handler" ist. Ich bin ziemlich sicher, dass Borland ein Patent auf eine Methode zur Implementierung davon hält.

0 Stimmen

Solange die Objekte in umgekehrter Reihenfolge der Erstellung zerstört werden, sind die Implementierungsdetails nicht wichtig, es sei denn, Sie sind ein Compiler-Ingenieur.

1 Stimmen

Abgestimmt abgelehnt: a) "Scott Meyers", nicht "S. Myers"; b) unrichtige Zitat: "Effektives C++": "Punkt 13: Fang Ausnahmen über Referenz.". Dadurch können Informationen an das Ausnahmeobjekt angepasst/hinzugefügt werden.

13voto

Sie könnten sich hier eine detaillierte Erklärung anschauen.

Es kann auch hilfreich sein, einen Trick in reinem C zu betrachten, um eine grundlegende Form von Ausnahmebehandlung zu implementieren. Dabei werden setjmp() und longjmp() auf folgende Weise verwendet: Erstere speichert den Stack, um den Ausnahmebehandler zu markieren (ähnlich wie "catch"), während letztere verwendet wird, um einen Wert zu "werfen". Der "geworfene" Wert wird so behandelt, als wäre er von einer aufgerufenen Funktion zurückgegeben worden. Der "try block" endet, wenn setjmp() erneut aufgerufen wird oder wenn die Funktion zurückkehrt.

10voto

Jules May Punkte 683

Ich weiß, dass dies eine alte Frage ist, aber es gibt hier eine sehr gute Darstellung, die sowohl die verwendeten Methoden in gcc als auch VC erklärt: http://www.hexblog.com/wp-content/uploads/2012/06/Recon-2012-Skochinsky-Compiler-Internals.pdf

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X