c++ exceptions implementierung
-
-
knivil schrieb:
Exceptions in C++ mittels throw unterscheidet sich von Exceptions/Traps in Betriebssystemen. Ein Aufruf von throw in C++ sollte kein Betriebssystemaufruf ausloesen. Es ist ganz normaler Programmcode wie if oder while ohne syscall.
MSVC verwendet SEH (ein Windows Feature) um C++ Exceptions zu implementieren.
Macht auch mächtig Sinn.
So kann man Exceptions "durch" alles durchwerfen was SEH unterstützt, und das ist auf Windows ziemlich viel.
U.a. das OS selbst, d.h. C++ Exceptions aus einer Callback-Funktion durch die OS Funktion durch und im C++ Programm wieder gefangen = kein Problem.Genau so C++ Exception aus einer Callback-Funktion durch eine DLL in irgendeiner Sprache (die halt SEH unterstützt) durch und wieder zurück nach C++.
Ist ne feine Sache.
-
knivil schrieb:
Es ist ganz normaler Programmcode wie if oder while ohne syscall.
Das Exception Handling der Itanium C++ ABI kannst du nicht mit normalem Programmcode nachprogrammieren.
Wenn da eine Exception auftritt wird der Instruction Pointer ausgelesen und in einer vom Ausführungscode unabhängigen Tabelle gesucht. Da steht dann der Handlercode. Es ist nicht nur unendlich mühsam, diesen Zero-Cost-Ablauf nachzuprogrammieren, sondern mit Standardmitteln schlicht unmöglich.
-
fbaetjased schrieb:
Es ist nicht nur unendlich mühsam, diesen Zero-Cost-Ablauf nachzuprogrammieren, sondern mit Standardmitteln schlicht unmöglich.
Unmöglich ist ein hartes Wort. Was daran könntest du denn nicht mit Standardmitteln nachprogrammieren?
Instructionpointer auslesen = magic value im Code beim Werfen der Exception
In einer vom Ausführungscode unabhängigen Tabelle suchen = Globaler Index der magic values
Handlercode anspringen und zum Catch springen = normale Funktionsaufrufe, bei Bedarf longjmpEs ist nicht nur unendlich mühsam
Das ist es wohl. Aber das kann ein Computerprogramm (Compiler) für uns erledigen.
Zero-Cost-Ablauf
Wären immer noch 0 Kosten, da die Verwaltungsdaten und Handlercode zur Compilezeit erzeugt wurden.
-
SeppJ schrieb:
Unmöglich ist ein hartes Wort. Was daran könntest du denn nicht mit Standardmitteln nachprogrammieren?
Instructionpointer auslesen = magic value im Code beim Werfen der ExceptionÜber den Instructionpointer kommt Itanium auch an den Callstack (sehr unexakt formuliert).
Wie programmierst du das hier nach?
[[noinline]] void f(int i) { if (i == 0) throw 0; } void g(int i) { lock_guard a; // lock1(); f(i); lock_guard b; // lock2(); f(i-1); // unlock2(); // unlock1(); }
-
Was soll der ganze Mist. Das ist das C++ Forum ...
Itanium C++ ABI
Und fuer welche Prozessoren/Systeme gibt es denn noch eine C++ ABI und wie unterscheiden sie sich? Ist das im C++ Standard festgelegt. Wenn es spezifisch fuer Winows sein soll, ab ins Windowsforum ...
Auch wird die Ausgangssituation vernachlaessigt:
user mode programm (also "unsere" applikation) macht throw->soft irq ...
wie es sich unter windows ce verhält bzw. wie man das rausfinden kann
Ja, man kann die Dokumentation speziell fuer Windows CE lesen.
-
knivil schrieb:
Und fuer welche Prozessoren/Systeme gibt es denn noch eine C++ ABI und wie unterscheiden sie sich?
Das Itanium-C++-ABI ist nicht unbedingt Itanium-spezifisch. Von einigen IA64-spezifischen Parts abgesehen wird es auch von anderen Compilern implementiert oder zumindest als Leitlinie genommen. Und zwar von jedem mir bekannten C++-Compiler, von MSVC- und Windows-Krams abgesehen
-
@SeppJ
Wie willst du ohne Compiler-Support die Tables erstellen die du brauchst um ohne das Pflegen von dynamischen "wo bin ich" Strukturen zur Runtime, einfach nur aus dem IP + Callstack zu ermitteln welcher Handler zuständig ist und wie das Unwinding ablaufen muss?In Assembler kann man das nachprogrammieren, ja (wenn auch EXTRAM mühsam). In C++ nicht, nichtmal mit Inline-Assembler.
-
hustbaer schrieb:
@SeppJ
Wie willst du ohne Compiler-Support die Tables erstellen die du brauchst um ohne das Pflegen von dynamischen "wo bin ich" Strukturen zur Runtime,Ja, ich hatte da an einen Trick gedacht, der aber nicht funktioniert hat.
Ich fürcht, es gehen nur die nicht-ganz-zero-cost Exceptions, wie man sie teilweise aus anderen Implementierungen kennt.
-
SeppJ schrieb:
Ich fürcht, es gehen nur die nicht-ganz-zero-cost Exceptions, wie man sie teilweise aus anderen Implementierungen kennt.
Das "unmöglich" war dadurch motiviert, dass die Fallbackimplementierung des GCCs das typische setjmp/longjmp ist. Wenns was besseres gäbe, hätten die das wahrscheinlich implementiert.
Ignoriert man die Grösse des Binaries sowie Startupzeit, geht es fast:
#include <iostream> #include <setjmp.h> void _throw_(int n); template <int N, int UniquePrime = 2> void f(int i) { if (i == 0) _throw_(UniquePrime*N); if (i == 10) _throw_(UniquePrime*UniquePrime*N); } void lock() { std::cout << "lock()\n"; } void unlock() { std::cout << "unlock()\n"; } void g_unwind1() { unlock(); std::cout << "leave g()\n"; } void g_unwind2() { unlock(); } template <int N, int UniquePrime = 3> void g(int i) { std::cout << "enter g("<<i<<")\n"; lock(); f<N*UniquePrime>(i); lock(); f<N*UniquePrime*UniquePrime>(i-1); g_unwind2(); g_unwind1(); } static jmp_buf buf; int main() { switch (setjmp(buf)) { // catch hat einmaligen Overhead, Weil jedes // Programm (mit Codeduplikation) so transformiert // werden kann, dass das catch im main steht. case 1: goto i1; case 2: goto i2; case 3: goto i3; case 4: goto i4; case 5: goto i5; } g< 5>( 2); // try { g(); } catch(...) {} i1: g< 7>( 1); // try { g(); } catch(...) {} i2: g<11>( 0); // try { g(); } catch(...) {} i3: g<13>(11); // try { g(); } catch(...) {} i4: g<17>(10); // try { g(); } catch(...) {} i5:; } void _throw_(int n) { std::cout << n << '\n'; switch (n) { case 2*3*3*7: g_unwind2(); g_unwind1(); longjmp(buf, 2); case 2*3*11: g_unwind1(); longjmp(buf, 3); case 2*2*3*3*13: g_unwind2(); g_unwind1(); longjmp(buf, 4); case 2*2*3*17: g_unwind1(); longjmp(buf, 5); default: std::cerr << "Table not complete: " << n << '\n'; } }enter g(2) lock() lock() unlock() unlock() leave g() enter g(1) lock() lock() 126 unlock() unlock() leave g() enter g(0) lock() 66 unlock() leave g() enter g(11) lock() lock() 468 unlock() unlock() leave g() enter g(10) lock() 204 unlock() leave g()Vollständig geht es nur, wenn Parameter+Stackvariablen in einem globalen Array abgelegt werden. In Anbetracht der oben ignorierten Nachteile würde ich aber immer noch als Zero-Cost bezeichnen.