2010-03-08 13 views
21

Im folgenden Code wird die stackbasierte Variable 'ex' in einer Funktion ausgelöst und abgefangen, die über den Bereich hinausgeht, in dem ex deklariert wurde. Das erscheint mir etwas seltsam, da (AFAIK) stack-basierte Variablen nicht außerhalb des Gültigkeitsbereichs verwendet werden können, in dem sie deklariert wurden (der Stack wird abgewickelt).Wie werden Ausnahmen, die auf dem Stack zugewiesen sind, über ihren Gültigkeitsbereich hinaus erfasst?

void f() { 
    SomeKindOfException ex(...); 
    throw ex; 
} 

void g() { 
    try { 
     f(); 
    } catch (SomeKindOfException& ex) { 
     //Handling code... 
    } 
} 

Ich habe eine print-Anweisung zu SomeKindOfException Destruktor hinzugefügt und es zeigt, dass ex zerstört wird, wenn es außerhalb des Gültigkeitsbereiches in f geht(), aber dann ist es in g gefangen() und zerstört wieder, sobald es aus geht Umfang dort auch.

Irgendwelche Hilfe?

+0

Stimmt es, hier eine Referenz zu verwenden? 'catch (SomeKindOfException & ex)' Ich denke, das ist gefährlich, da es den Kopierkonstruktor nicht aufruft und Sie Zugriff auf einen Speicherbereich erhalten, der zu dem zugeordneten Stack von f() gehört! Ich denke, dass dies sollte stattdessen korrekt sein: 'catch (SomeKindOfException ex)' – Dacav

+0

Es ist richtig (und noch besser - siehe http://www.parashift.com/c++-faq-lite/exceptions.html Abschnitt 17.7) durch Bezugnahme zu fangen. Als die Antworten auf meinen Fragezustand ist die Ausnahme, die abgefangen wird, nicht das stack-basierte Objekt, sondern eine Kopie davon, die an einem anderen Ort residiert, die den Stack-Abbau überstehen kann, daher gibt es kein solches Risiko. –

+0

Nun, ich habe gestern Abend ein Experiment durchgeführt und ja, es ist viel besser, die Referenzen zu verwenden. Sehen Sie sich das an: http: // Pastebin.com/8YQuNAux Wenn Sie es ausführen, können Sie feststellen, dass die Ausnahme bei jedem Fang ohne Verweis dynamisch zugeordnet wird (im Sinne von "neu"): Wenn Sie die Verweise verwenden, wird es stattdessen nur einmal zugewiesen und automatisch zerstört Der Umfang ist beendet. Ich denke auch, dass dieses Verhalten streng vom Compiler abhängt. – Dacav

Antwort

18

Das Ausnahmeobjekt wird an einen speziellen Ort kopiert, um den Stapel abzuwickeln. Der Grund dafür, dass Sie zwei Zerstörungen sehen, liegt darin, dass beim Beenden von f() die ursprüngliche Ausnahme zerstört wird und beim Beenden von g() die Kopie zerstört wird.

+0

Oh, das habe ich intuitiv erwartet, ich bin froh, dass es der Wahrheit entspricht. Auch der Kopierkonstruktor sollte aufgerufen werden, also wie gehst du richtig damit um? Angenommen, Ihre Ausnahmeklasse verfügt über eine Instanzvariable, die die Fehlermeldung enthält: Bei jedem nachfolgenden 'throw'-Befehl haben Sie eine neue Kopie der Variablen zugewiesen! Apropos Best Practice, was denkst du darüber, flache Kopien nur für alle Ausnahmen sicherzustellen? – Dacav

8

Das Objekt wird in ein Exception-Objekt kopiert, das das Stack-Abwickeln überlebt. Wo die Erinnerung für dieses Objekt herkommt, ist nicht spezifiziert. Für großes Objekt wird es wahrscheinlich malloc ed sein, und für kleinere Objekte könnte die Implementierung einen vorher zugewiesenen Puffer haben (ich könnte mir vorstellen, dass dies für eine bad_alloc Ausnahme verwendet werden könnte).

Der Referenz ex wird dann auf dieses Objekt Ausnahme gebunden, die eine vorübergehende (es keinen Namen hat).

+1

Danke für die Klärung des Speicherorts der Ausnahme, ich habe meinen obigen Kommentar bearbeitet, um diese Frage zu entfernen. –

1

Da die Spezifikation explizit angibt, dass ein temporäres Objekt anstelle des Operanden throw erstellt wird.

8

C++ Standard-15.1/4:

Der Speicher für die temporäre Kopie der Ausnahme geworfen wurde in einer nicht spezifizierten Weise zugeordnet ist, mit Ausnahme des in 3.7.3.1 festgestellt. Das temporäre bleibt bestehen, solange ein Handler für diese Ausnahme ausgeführt wird. Insbesondere, wenn ein Handler durch Ausführen eines Wurfs beendet wird; Anweisung, die Kontrolle an einen anderen Handler für die gleiche Ausnahme übergibt, so dass das temporäre bleibt. Wenn der letzte für die Ausnahme ausgeführte Handler auf andere Weise als throw beendet wird; das temporäre Objekt wird zerstört und die Implementierung kann den Speicher für das temporäre Objekt freigeben; Eine solche Freigabe wird auf unbestimmte Weise vorgenommen. Die Zerstörung erfolgt unmittelbar nach der Zerstörung des in der Exception-Deklaration im Handler deklarierten Objekts.

Es gibt nichts mehr zu sagen.

5

Wenn Sie ex abwerfen, wird es an einen speziellen Speicherort kopiert, der für ausgelöste Ausnahmeobjekte verwendet wird. Eine solche Kopie wird vom normalen Kopierkonstruktor ausgeführt.

können Sie diese leicht sehen aus diesem Beispiel:

#include <iostream> 

void ThrowIt(); 

class TestException 
{ 
    public: 
    TestException() 
    { 
     std::cerr<<this<<" - inside default constructor"<<std::endl; 
    } 

    TestException(const TestException & Right) 
    { 
     (void)Right; 
     std::cerr<<this<<" - inside copy constructor"<<std::endl; 
    } 

    ~TestException() 
    { 
     std::cerr<<this<<" - inside destructor"<<std::endl;  
    } 
}; 

int main() 
{ 
    try 
    { 
     ThrowIt(); 
    } 
    catch(TestException & ex) 
    { 
     std::cout<<"Caught exception ("<<&ex<<")"<<std::endl; 
    } 
    return 0; 
} 

void ThrowIt() 
{ 
    TestException ex; 
    throw ex; 
} 

Beispielausgabe:

[email protected]:~/cpp/test$ g++ -O3 -Wall -Wextra -ansi -pedantic ExceptionStack.cpp -o ExceptionStack.x 
[email protected]:~/cpp/test$ ./ExceptionStack.x 
0xbf8e202f - inside default constructor 
0x9ec0068 - inside copy constructor 
0xbf8e202f - inside destructor 
Caught exception (0x9ec0068) 
0x9ec0068 - inside destructor 

By the way, können Sie hier sehen, dass der Speicherplatz für das geworfene Objekt verwendet (0x09ec0068) ist definitiv weit entfernt von dem des ursprünglichen Objekts (0xbf8e202f): der Stapel hat wie üblich hohe Adressen, während der Speicher, der für das geworfene Objekt verwendet wird, ziemlich tief im virtuellen Adressraum ist. Dies ist jedoch ein Implementierungsdetail, da der Standard, wie andere Antworten darauf hingewiesen haben, nichts darüber aussagt, wo der Speicher für das geworfene Objekt sein sollte und wie es zugewiesen werden sollte.

+0

Unter Verwendung von VC++ ist der Speicherort des geworfenen Objekts sehr nahe an dem des ursprünglichen Objekts (d. H. Auf dem Stapel). Das beweist Johannes und Kirill wirklich, dass der Speicherort nicht näher spezifiziert ist. –

+0

Sie haben Recht, das ist wirklich ein Implementierungsdetail. Ich werde die Antwort klären. –

3

Zusätzlich zu dem, was der Standard in 15.1/4 sagt ("Ausnahmebehandlung/Auslösen einer Ausnahme") - dass der Speicher für die temporäre Kopie der ausgelösten Ausnahme in unspezifizierter Weise zugeordnet wird - ein paar andere Bits von Sicht, wie das Ausnahmeobjekt zugeordnet sind:

  • 3.7.3.1/4 („Zuordnungsfunktionen“) der Standard zeigt an, dass das Objekt Ausnahme kann nicht durch einen Ausdruck oder new einen Anruf zu einem zugeordnet werden 'globale Zuweisungsfunktion' (dh eine operator new() Ersetzung). Beachten Sie, dass malloc() keine "globale Zuweisungsfunktion" ist, wie im Standard definiert, so dass malloc() definitiv eine Option für die Zuweisung des Ausnahmeobjekts ist.

  • "Wenn eine Ausnahme ausgelöst wird, wird das Ausnahmeobjekt erstellt und ein im Allgemeinen auf irgendeine Art von Ausnahmedaten Stapel abgelegt" (Stanley Lippman, "im Inneren des C++ Objektmodell" - 7.2 Exception Handling)

  • Aus Stroustrups "Die C++ Programmiersprache, 3. Ausgabe": "Eine C++ Implementierung ist erforderlich, um genug Speicher zu haben, um bei Erschöpfung des Speichers bad_alloc zu werfen. Es ist jedoch möglich, dass das Auslösen einer anderen Ausnahme Speichermüdigkeit verursacht. "(14.4.5 Ressourcen Erschöpfung); und "Die Implementierung kann eine Vielzahl von Strategien zum Speichern und Übertragen von Ausnahmen anwenden. Es ist jedoch sichergestellt, dass genügend Speicher vorhanden ist, damit new die standardmäßige Ausnahme wegen Speicherauslastung, bad_alloc, auslösen kann" (14.3 Catching Exceptions) .

Beachten Sie, dass die Angebote von Stroustrup vor-Standard sind. Ich finde es interessant, dass der Standard nicht die Garantie zu geben scheint, die Stroustrup für wichtig hielt, um sie zweimal zu erwähnen.