ESP sichern?
-
Hallo!
Ich mache mich gerade daran, die Ackermann-Funktion in inline-assembler zu implementieren.int __stdcall ackermannASM(unsigned int x, unsigned int y) { unsigned int result = 0; __asm { mov eax, x //sollte schneller sein als immer im mem zu gucken mov ebx, y //hier ebenfalls xzero: //x auf null prüfen cmp eax, 0 //x == 0? jne xnzero //x != 0! add ebx, 1 //y++ mov result, ebx jmp end xnzero: cmp ebx, 0 //y == 0? jne xnzero_ynzero sub eax, 1 push ebx push eax call ackermannASM pop eax mov result, eax jmp end xnzero_ynzero: push eax //altes x auf dem stack sichern sub ebx, 1 push ebx push eax call ackermannASM pop ecx pop eax push ecx push eax call ackermannASM pop eax mov result, eax jmp end end: }; return result; }
Wenn ich die Funktion nun mit z.B. x = 1 und y = 1 aufrufe, dann bekomme ich immer einen Fehler:
"The value oof ESP was not properly saved across a function call. This is usually a result of calling a function declared with one calling convention with a function pointer declared with a different calling convention."Muss ich vor jedem Funktionsaufruf ein "push esp" packen?
Hier nochmal die Ackermannfunktion in C:
int ackermannC(unsigned int x, unsigned int y) { if(x == 0 && y > 0) { return y+1; } if(x > 0 && y == 0){ return ackermannC(x-1, 1); } if(x > 0 && y > 0) { return ackermannC(x-1, ackermannC(x, y-1)); } }
Danke schonmal für alle Antworten...
-
das ergebnis wird direkt in eax und nicht über den stack zurückgegeben, insofern hast du glück, das es überhaupt einigermassen kontrolliert abschmiert. der c++ rahmen mit dem result ist daher auch überflüssig (und zeitraubend). das jmp am ende bringt auch nichts, da das label ja gleich darauf folgt. ausserdem darf ebx nicht verändert werden - benutze eax/ecx/edx. alle anderen pops sind auch fehlerhaft, __stdcall sorgt dafür, dass die aufgerufene funktion den stack aufräumt.
-
Danke! Hat geholfen!
Kennt jemand eine gute Seite, auf der die verschiedenen Aufrufkonventionen erklärt werden?
Hier die Lösung (wen es interessiert):
int __stdcall ackermannASM(unsigned int x, unsigned int y) { __asm { mov ebx, x //sollte schneller sein als immer im mem zu gucken mov ecx, y //hier ebenfalls xzero: //x auf null prüfen cmp ebx, 0 //x == 0? jne xnzero //x != 0! add ecx, 1 //y++ mov eax, ecx jmp end xnzero: cmp ecx, 0 //y == 0? jne xnzero_ynzero sub ebx, 1 //x-- mov ecx, 1 //y = 1 push ecx push ebx call ackermannASM //Rückgabewert ist nach Konvention in eax gespeichert jmp end xnzero_ynzero: push ebx //altes x auf dem stack sichern sub ecx, 1 push ecx push ebx call ackermannASM pop edx //altes x wieder vom Stack holen push eax sub edx, 1 push edx call ackermannASM //Rückgabewert ist nach Konvention in eax gespeichert jmp end end: }; }
-
ebx wird immer noch verändert. das ist nicht zulässig - auch wenn es manchmal trotzdem funktioniert
-
Jo, ich werde mir da wohl noch was überlegen müssen.
Irgendwie gerät man schnell in Registernot beim i386 kompatiblen Rechnern. Wieso gibt es eigentlich nur so wenig Allzweckregister? Bei anderen Architekturen (z.B. SPARC) hat man wesentlich mehr.Hast Du eine Idee, wo man mal nach Aufrufkonventionen und so einem Zeug nachschlagen (Buch) bzw. nachgucken (Internetseiten) kann?
-
Die Aufrufkonventionen kannst du im Handbuch deines Compilers nachschlagen.
SPARC und andere RISC Architekturen haben so viele Allzweckregister, weil das einfach ein Bestandteil der RISC Architektur ist. RISC ist eben moderner, als dieser ekelige x86er Müll.
btw. versteh ich den Sinn nicht davon, die Funktion in Assembler umzuwandeln. Wahrscheinlich bist du sogar noch langsamer als dein Compiler.
-
sicherlich ist das langamer als das ergebnis des compilers - ich nehme eher an, dass es mehr um den lehreffekt ging. sonst könnte man gleich eine komplettlösung hinknallen:
__declspec( naked ) int __fastcall ackermannASM(unsigned x, unsigned y) { __asm { test ecx, ecx // x == 0 ? jz _x_is_zero test edx, edx // y == 0 ? __2: jz _y_is_zero __1: push ecx dec edx call __2 pop ecx mov edx, eax dec ecx jz _x_is_zero // tail recursion test edx, edx jnz __1 _y_is_zero: dec ecx mov edx, 1 jnz __1 // tail recursion _x_is_zero: lea eax, [ edx + 1 ] ret }; }
lerneffekt = 0; man könnte es genausogut iterativ machen