gcc/g++: Destruktoraufruf trotz geworfener Exception bei der Konstruktion
-
Hallo!
Mich verwundert das Verhalten des gcc/g++ auf zwei verschiedenen Systemen. Vielleicht hat ja einer von euch eine Erklärung dafür...
Ich habe eine Klasse deren Konstruktor eine Exception wirft, sofern nicht alle Ressourcen belegt werden konnten. Wird diese Exception geworfen, ist das Objekt nicht korrekt konstruiert und somit nicht existent. Also darf dafür auch kein Destruktor aufgerufen werden.
Kompiliere ich meinen Code unter Mac OS X 10.4 mit dem gcc 4.0.1 wird der Destruktor für ein Objekt welches nicht korrekt erstellt wurde auch nicht aufgerufen.
Kompiliere ich meinen Code unter Kubuntu 7.04 mit dem gcc 4.1.2 wird der Destruktor für ein nicht korrekt erstelltes Objekt aufgerufen. Dieser verursacht dann Speicherzugriffsfehler, da er auf nicht existente/nicht initialisierte Attribute zugreift.Komischerweise taucht der Fehler den ich erwarten würde nicht auf. Denn in der letzten Zeile müsste das "delete blah" einen Speicherzugriffsfehler verursachen, da es für ein nicht existierendes Objekt und nicht NULL aufgerufen wird...
Klasse* blah; try { blah = new Klasse(); } catch(Exception e) { std::cout << "Exception: " << e.getCause() << std::endl; } delete blah;
-
Wo wird denn der Destruktor unerwarteteterweise aufgerufen? Beziehst du dich dabei auf das Stack Unwinding oder auf den delete-Aufruf hinter dem try-Block? Und wie sehen der Ctor und Dtor deiner Klasse aus?
PS: delete auf einen undefinierten Zeiger verursacht nicht unbedingt einen SegFault, das ist ja das unangenehme an 'undefined behaviour' - da kann wirklich alles passieren von 'keine Reaktion' bis zu 'löst den vierten Weltkrieg aus'.
-
Zu Testzwecken habe ich innerhalb des Destruktors einfach eine Ausgabe auf cout:
std::cout << "Destruktor(" << anzahl << std::endl;
Der Konstruktor sieht mittlerweile so aus:
Klasse::Klasse() { anzahl = 5; throw Exception("test"); }
Aufgerufen wird der Destruktor nach Behandlung des try/catchs, also beim delete. (Nur beim Linux, nicht beim OSX.)
Die Frage die sich mir nun stellt ist, ob bei dem Teil wo der Destruktor aufgerufen wird einfach der Speicher auf den blah zeigt als Instanz der Klasse Klasse interpretiert wird, oder wirklich noch Fragmente im Speicher sind.
-
Das Pferd ohne Namen schrieb:
Die Frage die sich mir nun stellt ist, ob bei dem Teil wo der Destruktor aufgerufen wird einfach der Speicher auf den blah zeigt als Instanz der Klasse Klasse interpretiert wird, oder wirklich noch Fragmente im Speicher sind.
Wie ich schon sagte, das ist undefiniertes Verhalten - da kann theoretisch alles passieren. (und in deinem Fall sieht es so aus, als ob du dort tatsächlich einen Zeiger auf die Überreste des unfertigen Objekts in die Hand bekommen hast)
-
Das Pferd ohne Namen schrieb:
Aufgerufen wird der Destruktor nach Behandlung des try/catchs, also beim delete. (Nur beim Linux, nicht beim OSX.)
Wenn die Exception geworfen wird, darfst du nicht delete aufrufen. Darüber, was passiert, wenn du es doch tust, solltest du dir keine Gedanken machen.
-
CStoll schrieb:
(und in deinem Fall sieht es so aus, als ob du dort tatsächlich einen Zeiger auf die Überreste des unfertigen Objekts in die Hand bekommen hast)
Es könnte auch gut das drin sein, was vorher drin war, da der Zeiger nicht mit 0 initialisiert wird.
EDIT:
Der folgende Code sollte sogar vollkommen legal sein:Klasse* zeiger = 0; try { zeiger = new Klasse; } catch (...) { } delete zeiger;
Da nach meinem Verständnis die Zuweisung garnicht stattfindet, wenn new wirft. Damit ist der Ausdruck "delete 0" bei geworfener Exception, "delete <gültigerptr>" ansonsten. Ich meine das auch exakt so im Effective C++ gelesen zu haben, schaue aber heute abend nochmal nach.
-
Der Fehler liegt wohl beim Nichtinitialisieren des Zeigers. Ich habe gerade eben einfach einen Pointer deklariert und danach sofort ein delete auf ihn angewandt.
Klasse* blah; delete blah;
Wenn ich unter OSX kompiliere wird der Destruktor nicht aufgerufen, unter Linux schon.
Ich meine auch, dass der Code von LordJaxom in Ordnung ist. Denn wenn eine Exception im Konstruktor auftritt, wird die Bearbeitung unterbrochen und im Catch-Block fortgesetzt. Demzufolge bekommt der Pointer keinen Wert zugewiesen.
Was MFK schrieb ist meine ich nicht richtig, denn ansonsten müsste man stellenweise in Kauf nehmen, das Speicher nicht freigegeben wird.
Mich würde jetzt nur noch interessieren, ob ich die gcc auf beiden Maschinen so konfigurieren könnte, dass sie sich gleich verhalten...
Vielen Dank für die Hilfe,
Das Pferd ohne Namen
-
Das Pferd ohne Namen schrieb:
Mich würde jetzt nur noch interessieren, ob ich die gcc auf beiden Maschinen so konfigurieren könnte, dass sie sich gleich verhalten...
Vielleicht ist das bei dir noch nicht angekommen, aber wenn dein Konstruktor einen Exception wirft, erzeugt dein Code undefiniertes Verhalten.
Behebe diesen Fehler (die Initialisierung des Zeigers mit 0 ist eine Möglichkeit), statt zu versuchen, an den Compilern zu drehen, damit sie auf den Fehler gleich reagieren.