2016-04-06 12 views
2

Ich habe ein faktorielles Berechnungsprogramm geschrieben, das bis zu den endgültigen Berechnungen funktioniert. Wenn der faktorielle Wert in _multiply_fact berechnet wird, zeigt der Basiszeiger schließlich auf einen zufälligen Wert. Was mache ich falsch?Probleme mit _cdecl mit indirekter Rekursion

Aufruf

push n 
call _factorial 
add esp, 4 

Die Verfahren

_factorial: 
    push ebp   ;save base pointer 
    mov ebp, esp  ;set up new base pointer 
    mov ebx, 8[ebp] 

    cmp ebx, 1   ;while n > 0 
    jg _multiply_fact ;decrement n 
    mov eax, 1   ;if n == 0 ret 1 
    pop ebp 
    ret     ;return to multipy_fact for calculation 

_multiply_fact: 
    pop ebp  
    push ebp 
    mov ebp, esp 
    mov ebx, 8[ebp] 

    dec ebx     ;decrements n-1 times 
    push ebx 
    call _factorial 

    inc ebx     
    mul ebx     ;recursively multiplies eax * (n+1) 
    pop ebp 
    ret 

Antwort

0

Ordnung nach ein wenig mehr zur Fehlerbehebung fand ich, dass der Fehler verursacht wurde, weil ich

add esp, 4 

nach meinem Anruf nicht mit einberechnet _factorial in der _multiply_fact Prozedur

1

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