String-Klasse als Vorbild gesucht
-
Kannst du mal etwas genauer erklären was da nicht gehen soll? Wer weis von wo nach wo die Exception fliegen soll weis welche catchs und Dtoren auf dem Weg sind.
Bei deinem foo läst sich das wunderbar machen. Weder catch noch Dtor sind auf der Flugbahn.
-
Es gibt da einige Punkte, an denen ich mich stoße:
Da die Returnaddesse ja bereits at Compiletime bekannt ist
Wieso ist sie das? Warum weißt du zur Kompilierzeit, ob foo() jetzt von bar1() oder bar2() aufgerufen wurde? Gehst du jetzt einfach mal von Inlining aus?
Du gehst anscheindend auch immer davon aus, dass beim Werfen einer immer die gleichen Objekte zerstört werden. Was zerstört wird, kann jedoch sogar noch vom catch-Block abhängen.
void foo() { string x; try { throw BlubberException() } // zerstör nichts catch( BlubberException& ) { if( blubb ) throw; // zerstör x // zerstör nichts } foo2(y); if( bla ) throw BlubberException(); // zerstör x und y // ... }
Wie willst du diese ganzen Fälle unterscheiden? Du kannst nicht einfach sagen, wenn die BlubberException auftritt, musst du das und as zerstören. Du musst genau wissen, in welchem Block du bist und um was für eine Exception es sich handelt.
Dann noch was:
Angenommen throwMe() wirft ne Exception. catchMe() würde sie auffangenException at throwMe()
at bla()
at blubb()
at main()Hier wird nichts gefangen. Das musst du wissen. Das weißt du aber nicht. Mit deinem System kennst du nur die Funktion wo du gerade bist und welche Exception jetzt anliegt.
Exception at throwMe()
at bla()
at catchMe()
at blubb()
at main()Hier wird sie gefangen. Weil blubb() diesmal (d.h. weil rand()%2 diesmal 0 war) nicht direkt throwMe() aufgerufen hat sondern erst catchMe().
Exception at throwMe()
at hoi()
at bla()
at catchMe()
at blubb()
at catchMe()
at main()Jetzt wirds noch schöner. Jetzt musst du wissen welches catchMe das auffängt. Das ist nicht unwesentlich. Was willst du machen?
-
Da die Returnaddesse ja bereits at Compiletime bekannt ist
Wieso ist sie das? Warum weißt du zur Kompilierzeit, ob foo() jetzt von bar1() oder bar2() aufgerufen wurde? Gehst du jetzt einfach mal von Inlining aus?
void foo(int a){ if(a==1) throw 4; } void foo2(){ foo(1); }
foo_ret_table: cmp eax foo2.retaddress jne .next ;foo wurde as foo2 aufgerufen .next: foo: cmp [esp+4] 1 jne .else pop eax ;Return Address mov ebx 4 ;die Exception add esp 4 ;Mach den Stack sauber von Argumenten (Dtoren) jmp foo_ret_table .else: ret 4 foo2: push 1 call foo .retaddress:
call foo is eigentlich das gleiche wie:
push .retaddress jmp foo
Und das ret 4 in foo tut dies:
pop eax add esp 4 jmp eax
(ohne eax zu verändern)
foo und foo2 sind nur labels die an den Stellen wo man sie benutzt durch Numbern ersetzt werden, wie das jetzt genau lauffähig ist weis ich nur es sind immer die gleichen Numberen!
Du gehst anscheindend auch immer davon aus, dass beim Werfen einer immer die gleichen Objekte zerstört werden. Was zerstört wird, kann jedoch sogar noch vom catch-Block abhängen.
Ich hab nie gesagt, dass verschiedene throws in der gleichen Funktion nicht verschiedene Dtoren aufrufen können, sondern die die für ein bestimmt throw auf der Flugbahn liegen:
void foo(){ string a; { string b; throw 4; } throw 2; }
foo: sub esp sizeof(string) ;string a push esp call string::string sub esp sizeof(string) ;string b push esp call string::string push esp ;throw 4 call string::~string add esp sizeof(string) push esp call string::~string add esp sizeof(string) pop eax mov ebx 4 jmp foo_ret_table push esp call string::~string add esp sizeof(string) push esp ;throw 2 call string::~string add esp sizeof(string) pop eax mov ebx 2 jmp foo_ret_table push esp call string::~string add esp sizeof(string) ret
Die throws blähen den Code zwar ein wenig auf aber die kann man ja auch noch auslagern wenn man Angst vor langen jmps im normalen Code hat.
Du musst genau wissen, in welchem Block du bist und um was für eine Exception es sich handelt.
Ich code jetzt nicht noch mal einen Beispiel zusammen aber im foo_ret_table stehten die Funktionsaufrufe (!=Funktion) und es ist at compile time klar in welchem Block ein Funktionsaufruf steht.
Das musst du wissen. Das weißt du aber nicht. Mit deinem System kennst du nur die Funktion wo du gerade bist und welche Exception jetzt anliegt.
Nein du weis auch noch wo du in der Funktion bist. Ich hab jetzt kein Beispiel mit einem catch Block gecodet, aber zwischen den Dtoren muss man nur die typeids vergleichen und man weis ob man weiter fliegen muss oder in den catch Block springen.
Jetzt wirds noch schöner. Jetzt musst du wissen welches catchMe das auffängt. Das ist nicht unwesentlich. Was willst du machen?
Das ist ein non Problem. Der einzige Unterschied zwischen 2 zweimal dem gleichen catch Block ist, dass noch ein paar mehr Daten auf dem Stack sind. Wenn man die nicht wegputzt ist alles ok, und ich putz sie ja nicht weg.
-
Irgendwer schrieb:
foo und foo2 sind nur labels die an den Stellen wo man sie benutzt durch Numbern ersetzt werden, wie das jetzt genau lauffähig ist weis ich nur es sind immer die gleichen Numberen!
Man, das ist doch nur in deinem speziellen Fall so. Ich kann foo() von überall aus aufrufen. Ich kann foo() sogar sich selber aufrufen lassen. Ich kann foo aus ein und der selben Funktion an 80 verschiedenen Stellen mehrmals aufrufen. Genausowenig wie du alles inlinen kannst, kannst du die Return Adresse immer zur Kompilierzeit wissen. Du bestimmst deine ret Adresse auch nur über 50 cmp-Aufrufe und branches. Das nenne ich nicht "zur Compilierzeit bekannt".
Überlass das den Compiler. Der ist schon nicht so blöd wie du glaubst. Natürlich garantiere ich dir nicht, dass der Compiler nicht den Eintritt in einen try-Block nicht doch mal wegoptimieren kann, so wie er auch mal ne ganze Schleife wegoptimieren kann.
Trotzdem ändert das nichts am allgemeinen Problem.Es ist _unmöglich_ für den Compiler, jedesmal, wenn ich noch einen Aufruf von foo() *irgendwo* im Code einbaue, dass er die foo_ret_table updates und noch ein 180stes cmp einfügt.
Ich kann auch mal ne externe Funktion aufrufen. C++ übersetzt getrennt. Es geht nicht. Es spricht viel zu viel dagegen. Sogar die Logik. Wieso soll ich einen Funktion verändern (bzw. etwas, was dazugehört), weil ich sieo jetzt noch einmal mehr aufrufe??Das _jeder_ Eintritt und Austritt in einem try-Block registriert wird ist die einzige Möglichkeit, dass es immer geht. So teuer ist es doch gar nicht. Was ist denn ein push? Ein add auf den Zeiger und nochmal eine Handvoll bytes darauf kopieren.
Sicherlich wird der Compiler auch ab und zu das wegoptimieren können. Ich würde mich da gar nicht einmischen. Solltest du weiter der festen Überzeugung sein (ich bin es nicht), dass dein System funktioniert, kannst du das ja mal den Compilerbauern erklären.
-
Vielleicht kann jemand eine string-Klasse posten, die als Vorbild tauglich und möglichst performant ist?
-
Es können sich auch ein paar zusammen tun und eine entwickeln.