Ruft longjmp in C++ Destruktoren auf?
-
Ich konnte im C++ Standard zu
longjmp
nur dies hier finden:18.7 Other runtime support, Absatz 4 schrieb:
The function signature
longjmp(jmp_buf jbuf, int val)
has more restricted behavior in this International Standard. If any automatic objects would be destroyed by a thrown exception transferring control to another (destination) point in the program, then a call tolongjmp(jbuf, val)
at the throw point that transfers control to the same (destination) point has undefined behavior.Bin daraus aber nicht schlau geworden.
Bevor jemand fragt, wieso ich
longjmp
einsetze:
Habe hier eine C Bibliothek, welche damit arbeitet. In Callbacks möchte ich mit C++ Objekten arbeiten, wobei ich darin auch wieder Funktionen aus der Bibliothek aufrufe. Diese Funktionen könnten einenlongjmp
auslösen.Grüssli
-
Also die MSDN-Seite zu longjump sagt eindeutig
When you include setjmpex.h or setjmp.h, all calls to setjmp or longjmp will result in an unwind that invokes destructors and finally calls. This differs from x86, where including setjmp.h results in finally clauses and destructors not being invoked.
A call to setjmp preserves the current stack pointer, non-volatile registers, and MxCsr registers. Calls to longjmp return to the most recent setjmp call site and resets the stack pointer, non-volatile registers, and MxCsr registers, back to the state as preserved by the most recent setjmp call.
Auf cplusplus.com steht auch noch
The function never returns to the point where it has been invoked. Instead, the function transfers the control to the point where setjmp was used to fill the env parameter.
So würde es für mich Sinn machen, dass ein Dtor aufgerufen wird. Wenn das Scope so radikal verlassen wird (bzw. werden kann), ist das ja sogar vernünftig imo...
Gruß
PuerNoctis
-
PuerNoctis schrieb:
Also die MSDN-Seite zu longjump sagt eindeutig
When you include setjmpex.h or setjmp.h, all calls to setjmp or longjmp will result in an unwind that invokes destructors and finally calls. This differs from x86, where including setjmp.h results in finally clauses and destructors not being invoked.
A call to setjmp preserves the current stack pointer, non-volatile registers, and MxCsr registers. Calls to longjmp return to the most recent setjmp call site and resets the stack pointer, non-volatile registers, and MxCsr registers, back to the state as preserved by the most recent setjmp call.
Du solltest dir schon anschauen, was du zitierst und auch gleich die Quelle mitangeben.
Hier die Quelle: http://msdn.microsoft.com/en-us/library/36d3b75w.aspx
Das ist für x64 Programmierung.PuerNoctis schrieb:
Auf cplusplus.com steht auch noch
The function never returns to the point where it has been invoked. Instead, the function transfers the control to the point where setjmp was used to fill the env parameter.
Was auf cplusplus.com steht, weiss ich auch. Aber das hilft einem überhaupt nichts dabei herauszufinden, ob eine Garantie besteht, dass die Destruktoren aufgerufen werden. Was setjmp und longjmp tun, weiss ich schon. Allerdings kenne ich es nur aus C und weiss nicht, wie die Destruktoren sich damit vertragen.
PuerNoctis schrieb:
So würde es für mich Sinn machen, dass ein Dtor aufgerufen wird. Wenn das Scope so radikal verlassen wird (bzw. werden kann), ist das ja sogar vernünftig imo...
Sinn machen heisst aber nicht, dass es garantiert ist. Gibt noch vieles was Sinn machen würde und im Standard trotzdem nur als "implementation definied" drin steht.
Wenn wir übrigens bei der MSDN bleiben, dann bekommen wir das hier:
http://msdn.microsoft.com/en-us/library/yz2ez4as.aspxEs wird dringend davon abgeraten. Anscheinend geht es mit der /EH Option beim MSVC. Unten steht aber, dass man sich darauf nicht verlassen sollte, wenn man portierbaren Code schreiben möchte (was auch immer Microsoft damit genau meint). So eindeutig ist es somit nicht.
Ich möchte nun halt gerne wissen, wie es wirklich definiert ist. Mutmassungen bringen mir nichts.
Grüssli
-
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