Ruft longjmp in C++ Destruktoren auf?
-
es ist nicht möglich longjmp effizient zu implementieren wenn man alle destruktoren aufrufen will. deshalb nehme ich schwer an, dass dies UB ist.
beim vc++ ist dies zB nicht erlaubt (also über die objektscopes zu springen):
http://msdn.microsoft.com/en-us/library/3ye15wsy(v=VS.80).aspx schrieb:
Be careful when using setjmp and longjmp in C++ programs. Because these functions do not support C++ object semantics, it is safer to use the C++ exception-handling mechanism.
-
"has undefined behaviour" ist doch hinreichend eindeutig, oder nicht?
-
Bashar schrieb:
"has undefined behaviour" ist doch hinreichend eindeutig, oder nicht?
Ich bin mir allerdings nicht sicher, ob ich den Teil davor richtig verstanden haben. Deshalb schrieb ich auch, dass ich daraus nicht ganz schlau geworden bin. Wenn meine Hirnzellen das richtig verarbeiten, dann steht dort, dass wenn automatische Objekte zerstört werden, weil eine Exception von A nach B geflogen ist, dann ist es undefiniert, ob diese auch zerstört werden, wenn ein
longjmp
von A nach B durchgeführt wird.(Vielleicht hoffen meine Hirnzellen auch nur einfach, dass sie dies falsch verarbeiten :D)
Grüssli
-
Nicht ganz. Es nicht nicht undefiniert, ob die Objekte zerstört werden. Das ganze Verhalten ist undefiniert.
Spekulation: Wahrscheinlich hätte man sagen können, dass es unspezifiziert ist, ob die Objekte zerstört werden. Oder sogar, dass sie nicht zerstört werden. Da in solchen Fällen allerdings das ganze Lebenszyklus-Modell eines Objektes zusammenbricht dürfte man sich auf die sichere Ebene "undefiniert" (sprich: longjmp nur in reinem C bitte, wenn es gar nicht anders geht) geeinigt haben.
-
Dravere schrieb:
Bashar schrieb:
"has undefined behaviour" ist doch hinreichend eindeutig, oder nicht?
Ich bin mir allerdings nicht sicher, ob ich den Teil davor richtig verstanden haben. Deshalb schrieb ich auch, dass ich daraus nicht ganz schlau geworden bin. Wenn meine Hirnzellen das richtig verarbeiten, dann steht dort, dass wenn automatische Objekte zerstört werden, weil eine Exception von A nach B geflogen ist, dann ist es undefiniert, ob diese auch zerstört werden, wenn ein
longjmp
von A nach B durchgeführt wird.(Vielleicht hoffen meine Hirnzellen auch nur einfach, dass sie dies falsch verarbeiten :D)
Grüssli
fast. Dort steht, dass wenn automatische Objekte zerstört werden würden (d.h. nicht-PODs, denn PODs werden nicht zerstört sondern nur freigegeben), falls eine Exception per throw von A nach B fliegen würde, dann löst die Verwendung von longjmp zum gleichen Zielpunkt B an Stelle des throws undefiniertes Verhalten aus.
Es steht nicht da, dass Destruktoren nicht ausgeführt werden: das würde nämlich das Verhalten spezifizieren und gerade nicht undefiniert lassen.Das ganze ist deshalb so umständlich formuliert, weil longjmp natürlich immer noch dort funktionieren soll, wo im Grunde nur reines C verwendet wird.
-
Danke, bestätigt somit meine Befürchtungen endgültig.
Mal sehen, wie ich das nun am besten löse...
Grüssli
-
Wahrscheinlich musst du an den kritischen Stellen (die Zeit, in der das böse
longjmp()
aufgerufen werden könnte) auf reines C zurückgreifen, zumindest was die Objektsemantik betrifft. Andere C++-Features wie Templates kannst du ja nach wie vor verwenden. Vielleicht besteht eine Möglichkeit darin, C++-Objekte mit einem C-Interface zu wrappen, wie es in Bindings getan wird. Dann musst du die Wrapper-Objekte zwar manuell konstruieren und zerstören, aber kannst intern normal arbeiten.Das Problem ist aber nicht nur ein C++-spezifisches. Wie löst man es in C, wenn eine aufgerufene Funktion den Programmkontext neu setzen kann? Falls die Kontrolle nicht direkt zum Aufrufer zurückgelangt, wird es mit Speicherfreigabe etc. unter Umständen schwierig. Überhaupt stelle ich mir das Arbeiten mit
setjmp()
undlongjmp()
ziemlich übel vor...
-
Nexus schrieb:
Wahrscheinlich musst du an den kritischen Stellen (die Zeit, in der das böse
longjmp()
aufgerufen werden könnte) auf reines C zurückgreifen, zumindest was die Objektsemantik betrifft. Andere C++-Features wie Templates kannst du ja nach wie vor verwenden. Vielleicht besteht eine Möglichkeit darin, C++-Objekte mit einem C-Interface zu wrappen, wie es in Bindings getan wird. Dann musst du die Wrapper-Objekte zwar manuell konstruieren und zerstören, aber kannst intern normal arbeiten.Aktuell sieht es so aus, dass ich womöglich folgendes machen kann:
begin callback some longjmp danger code normal c++ code without longjmp some longjmp danger code end callback
So könnte ich eine Funktion aufrufen, welche dann den C++ Code enthält oder halt einfach einen zusätzlichen Block einführen.
Nexus schrieb:
Das Problem ist aber nicht nur ein C++-spezifisches. Wie löst man es in C, wenn eine aufgerufene Funktion den Programmkontext neu setzen kann? Falls die Kontrolle nicht direkt zum Aufrufer zurückgelangt, wird es mit Speicherfreigabe etc. unter Umständen schwierig. Überhaupt stelle ich mir das Arbeiten mit
setjmp()
undlongjmp()
ziemlich übel vor...Naja, grundsätzlich gleich wie mit Exceptions. Gewisse Kompiler verwenden
setjmp
undlongjmp
um Exceptions umzusetzen. In C muss man halt viel Code selber hinschreiben, welcher der Kompiler in C++ hinschreiben würde. In C besteht somit die Gefahr, dass man was vergisst. Man kann da auch mit Makros rumtricksen, aber auch da muss man dann auf gewisse Dinge achten. Möglich ist es definitiv und kann unter Umständen auch die Fehlerbehandlung etwas erleichtern. Also ellenlange if-else Prüfungen vernichten.Aber es ist definitiv Vorsicht geboten
Grüssli
-
Dravere schrieb:
So könnte ich eine Funktion aufrufen, welche dann den C++ Code enthält oder halt einfach einen zusätzlichen Block einführen.
Ja, das ist natürlich das Einfachste, wenn die bösen Funktionen nicht auf Daten deiner C++-Objekte zugreifen und dabei unerwartete Sprünge aus deren Scope durchführen.
Dravere schrieb:
Aber es ist definitiv Vorsicht geboten
C++-Exceptions passen vor allem sehr gut zu RAII. Ohne automatische Zerstörung würde man den grossen Vorteil der Exceptions einbüssen, nicht auf jeder Ebene der Aufrufhierarchie Fehlerbehandlung und Aufräumaktionen durchführen zu müssen.
Aber RAII kann ja
longjmp()
nicht. Wie kann man also Speicher freigeben, wenn eine Funktion den Scope zu verlassen droht? Einen Callback einrichten und jeweils neu mitgeben? An so eine Situation denke ich:// C++ Obj obj; FunktionMitThrow(); // kein Problem // C Obj* obj = CreateObj(); FunktionMitLangemSprung(); DestroyObj(obj); // evtl. zu spät
Sorry, falls das etwas OffTopic ist. Vielleicht sollte ich die Frage auch im C-Forum stellen...
-
Nexus schrieb:
C++-Exceptions passen vor allem sehr gut zu RAII. Ohne automatische Zerstörung würde man den grossen Vorteil der Exceptions einbüssen, nicht auf jeder Ebene der Aufrufhierarchie Fehlerbehandlung und Aufräumaktionen durchführen zu müssen.
Aber RAII kann ja
longjmp()
nicht. Wie kann man also Speicher freigeben, wenn eine Funktion den Scope zu verlassen droht? Einen Callback einrichten und jeweils neu mitgeben? An so eine Situation denke ich:// C++ Obj obj; FunktionMitThrow(); // kein Problem // C Obj* obj = CreateObj(); FunktionMitLangemSprung(); DestroyObj(obj); // evtl. zu spät
Sorry, falls das etwas OffTopic ist. Vielleicht sollte ich die Frage auch im C-Forum stellen...
Du kannst dies grundsätzlich einfach über ein künstliches finally erreichen. Du musst dazu natürlich
jmp_buf
kapseln und immer wenn du ein neuerjmp_buf
einführst, musst du den vorherigen speichern. Also grundsätzlich einen zusätzlichen Stack mitführen.Also etwas pseudomässig:
int doRethrow = 0; Obj* obj = CreateObj(); push_jmp_frame(); // thread_jmp_frame verweist auf ein neues Objekt. if(setjmp(thread_jmp_frame->buf) == 0) { // try FunktionMitLangemSprung(); } else { // catch doRethrow = 1; } pop_jmp_frame(); // finally DestroyObj(obj); if(doRethrow) { rethrow(); }
Irgendetwas in der Art.
Grüssli
-
Ah, klingt interessant, danke. Mit Makros könnte man das sicher noch "benutzerfreundlich" machen.
Ich sehe schon, ich muss wieder mal in Ruhe experimentieren...
-
Nexus schrieb:
Ah, klingt interessant, danke. Mit Makros könnte man das sicher noch "benutzerfreundlich" machen.
Jein, darüber kann man ziemlich streiten. Ich habe bis heute keine Implementation gesehen, wo du dann im TRY-CATCH-FINALLY Bereich einfach ein
return
hinsetzen konntest. Wegen den Makros wird dies dann sogar versteckt und es steht nur in der Dokumentation, dass dies nicht geht. Das ist natürlich sehr gefährlich, so hast du plötzlich einen Frame nicht entfernt. Dieser Fehler fällt womöglich auch nicht sofort auf, bis irgendwann ein longjmp bis an den Anfang zurückgehen soll. Der Fehler bleibt also vielleicht Jahre lang unentdeckt, die Software läuft Jahre lang ... und dann plötzlich passiert ein äusserst seltsamer Fehler, welche garantiertes undefiniertes Verhalten auslöst und die ganze angeschlosssene Datenbank löscht - Licht ausAlso mal ganz unter uns C++'ler:
Bin ich froh Exceptions und RAII zu haben!Grüssli