使用间接递归的_cdecl问题

时间:2016-04-06 05:48:46

标签: assembly cdecl

我已经写了一个阶乘计算程序,直到最后的计算为止。当它计算_multiply_fact中的阶乘值时,基指针最终指向随机值。我做错了什么?

调用

push n
call _factorial
add esp, 4

程序

_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

2 个答案:

答案 0 :(得分:1)

正如你所说,阻止它工作的错误是在递归调用之后没有修复堆栈。但并非唯一的错误

_multiply_fact实际上不是一个功能。它只是_factorial代码的一部分,当您没有采用早期n <= 1返回路径时,它会运行。所以你应该在其中制作堆栈帧。

pop ebp顶部的_multiply_fact完全是假的。它只有可以工作,因为运行时堆栈的顶部已经有调用者ebp。如果您直接将其作为一项功能进行调用,则可以使用返回地址对来电者ebp进行删除。

首先,您根本不需要制作堆栈框架,因此完全浪费了堆栈空间和指令。 (虽然它确实有助于调试器做回溯,因为没有它,他们需要编译器通常添加的信息,但手写的asm函数除非你手动添加,否则不会有。)

您的_factorial也打电话给来电者ebx,这违反了ABI。我认为你的功能实际上不会得到正确的值,因为它取决于ebx_factorial的呼叫中存活。在所有递归调用返回ebx=1之后,因为每个嵌套的调用都使用它的arg执行ebx。

因为你在asm中写作,你可以让你的递归自我调用假设哪些寄存器没有被破坏,或者如果你想要的话甚至可以在regs中传递args。不过,您仍然需要以某种方式在递归调用中保存n。一种选择是利用这样一个事实:你知道_factorial并没有在堆栈上破坏它的arg(即使ABI允许它)。

但是仍然需要可公开访问的包装函数来遵循ABI。

显然,与循环相比,递归对于阶乘而言是一个很大的浪费时间,但这里的版本尽可能少。

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

答案 1 :(得分:0)

好了,经过多一点故障排除后我发现错误是因为我忘了包含

add esp, 4

在_multiply_fact过程中调用_factorial后