setjmp / longjmp
« | 23 Feb 2019 | »In einer Zeit, als die alten Götter herrschten, schrie das Land, das Kriegsherrn in Aufruhr versetzten, nach …
Exceptions, weil weder Hercules noch Xena ein Konzept zur strukturierten Fehlerbehandlung vorweisen konnten.
C++ wurde erst 1998 vollständig standardisiert und die anderen Sprachen … gab es die eigentlich schon?
Doch einer der ältesten Titanen, nämlich C, zeigte sich unbeeindruckt, da er schon lange über ein Technik verfügte, in “Ausnahmefällen” quer durch den Code zu springen.
Die Rede ist von den oft vergessenen Funktionen setjmp()/longjmp() und die beiden stellen aus meiner bescheidenen Sicht die Krönung der Hardware- Abstraktion in einer Hochsprache dar.
setjmp()
sichert den aktuellen CPU Status in einem Puffer und arbeitet
dann wie jede andere Funktion linear im Code weiter.
Wird jedoch später longjmp
aktiviert, kommt es zur Wiederherstellung
des gespeicherten Zustandes und egal wo in welcher Unterfunktion man
gerade Code abarbeitete, man landet wieder dort, wo man zuvor setjmp
ausgeführt hatte.
Syntaktisch Funktionen auch Exceptions so. Man umklammert einen kritischen
Block mit try { ... }
und landet bei einem Fehler (Exception) im
nachfolgenden catch(...) { ... }
Block.
setjmp
teilt über seinen Rückgabewert mit, ob es sich um ein “Setzen” des
Sprungpunktes handelte oder eine Rücksprung zu diesem.
Man könnte also fast glauben, C hätte einen Exception Mechanismus und man
könnte C++ Exceptions mit setjmp()/longjmp()
umsetzen.
Ganz so einfach ist es aber nicht, denn es handelt sich eben nur um einen
gezielten Sprung. In anderen Funktion geöffnete Ressourcen bleiben weiter
offen, da sie niemand schließt und folglich ist der Zustand des Programms
nach einem longjmp()
in vielen Fällen korrumpiert.
Wenn, dann müsste man (am besten automatisiert) um jeden Funktionsaufruf
einen setjmp()/longjmp()
mit einem eigenen Kontextpuffer basteln, der lokale
Spezialitäten freigibt, so wie es in C++ durch Destruktoren erledigt wird.
Denn C++ Exceptions garantieren, dass im Falle von throw ...
jede laufende
Funktion “rückabgewickelt” wird, bis man den try-catch
Block erreicht hat.
Eine solche Garantie kennen setjmp/longjmp leider nicht.
Einsatzort
Tja, wo setzt man also solche Konstrukte heute noch ein?
Nun es gibt C-Bibliotheken, die in Fehlerfällen
abort()
oder
exit()
aufrufen, was einem ziemlich den Tag versauen kann.
Die libpng ist so ein Teufelchen.
Sie lässt einen nur eine Notfall-Funktion registrieren, die aufgerufen wird um nach Hilfe zu rufen, beendet aber nach deren
Rückkehr den Prozess, zumindest war dies in früheren Versionen so.
Einzige Lösung ist der libpng mit einem longjmp die Kontrolle zu entziehen.
Man kann longjmp()
aber auch in Signalhandlern einsetzen, um sich vor einem
tödlichen [SEGFAULT](http://de.wikipedia.org/wiki/Schutzverletzung)
zu
schützen.
Signale werden ähnlich wie normale Funktionsaufrufe in den laufenden Code
injiziert. Unterbricht der Prozessor die Ausführung wegen eines solchen
schweren Fehlers, findet die Ausführung über (genauer genommen ‘unter’) dem
Context der zuletzt ausgeführten Funktion statt.
Und von hier aus kann man zum letzten “Fail-Safe” Punkt zurückspringen.
Andernfalls würde ebenfalls nach dem Signalcode der Prozess beendet werden.
Fazit
Jeder C Programmierer, die noch nie von der Existenz von setjmp()/longjmp()
gehört hat, sollte sich wieder mal die Zeit nehmen und im Handbuch
nachschlagen.
Diese Funktionen sind besonders in der Systemprogrammierung hilfreich um Kollateralschäden durch defekte Komponenten zu bekämpfen. Null-Pointer und andere tödliche Programmierfehler können so zur Laufzeit abgefangen werden und das Programm in einen (einigermaßen) stabilen Zustand überführen.