Wie Sie sagen, der Fehler, der es daran hinderte zu arbeiten, war das Fehlen des Fixierens des Stapels nach dem rekursiven Aufruf. Aber das war nicht der einzige Bug
_multiply_fact
ist nicht wirklich eine Funktion. Es ist nur ein Teil des Codes _factorial
, der ausgeführt wird, wenn Sie nicht den früheren n <= 1
Rückpfad verwenden. Also solltest du nicht einen Stack Frame drin machen.
Die pop ebp
an der Spitze _multiply_fact
ist total falsch. Es nur funktioniert, weil die Spitze des Stapels, wenn das bereits ausgeführt wird, ebp
des Aufrufers hat. Wenn Sie es direkt als Funktion aufrufen würden, würden Sie die ebp
des Anrufers mit der Absenderadresse überlisten.
Zuerst müssen Sie überhaupt keinen Stapelrahmen machen, es ist nur eine Verschwendung von Stapelplatz und Anweisungen. (Obwohl es tut Hilfe Debugger tun Backtraces, denn ohne sie ihnen Informationen benötigen, die normalerweise hinzufügen Compiler, sondern handgeschriebene asm Funktionen nicht haben, wenn Sie es manuell hinzufügen.)
Ihre _factorial
auch den Anrufer clobbers ebx
, die verstößt gegen die ABI. Ich glaube nicht, dass Ihre Funktion tatsächlich den richtigen Wert erhalten wird, da es von ebx
abhängt, über den Anruf zu _factorial
zu überleben. Nachdem alle rekursiven Aufrufe zurückgegeben werden, ebx=1
, weil jeder verschachtelte Aufruf ebx mit seinem arg klobbert.
Da Sie jedoch in asm schreiben, können Sie Ihre rekursiven Selbstaufrufe Annahmen darüber machen, welche Register nicht geblockt werden, oder sogar Argumente in regs übergeben, wenn Sie möchten. Sie müssen jedoch n
über den rekursiven Aufruf irgendwie speichern. Eine Möglichkeit besteht darin, die Tatsache auszunutzen, dass Sie wissen, dass _factorial
seinen arg auf dem Stapel nicht klarkommt (obwohl der ABI es zulässt).
Sie benötigen dennoch die öffentlich zugängliche Wrapper-Funktion, um der ABI zu folgen.
Offensichtlich ist Rekursion in erster Linie eine große Zeitverschwendung für Fakultät, im Vergleich zu einer Schleife, aber hier ist eine Version, die so wenig wie möglich saugt.
global _factorial
_factorial:
; one arg: int n
push ebp
mov ebp, esp ; make a stack frame
mov edx, [ebp + 8] ; load first arg into a scratch reg
dec edx
jg .multiply_fact ; if ((n-1) > 0). Note that dec doesn't set CF, so just use jnz instead of ja if you want to convert this to unsigned
;; base case
mov eax, 1 ;if (n <= 1) ret 1
pop ebp
ret
.multiply_fact: ; not a separate function, still part of _factorial
; in NASM, .labels are local labels that don't show up as symbols in the object file. MASM uses something different.
; at this point: edx = n-1
push edx
call _factorial ; eax = fac(n-1)
pop edx ; get n-1 back off the stack. Taking advantage of extra knowledge of our own internals, since the ABI allows functions to clobber their args on the stack
; add esp, 4 not needed, because we popped instead
inc edx ; undo the earlier dec before we pushed
imul eax, edx ; n * fac(n-1). 2-arg imul runs faster
pop ebp
ret