2

我编写了一个阶乘计算程序,可以一直工作到最终计算。当它计算 中的阶乘值时_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
4

2 回答 2

1

正如您所说,阻止它工作的错误是在递归调用后缺乏修复堆栈。但这不是唯一的错误

_multiply_fact不是真正的功能。它只是_factorial's 代码的一部分,它在您不采用提前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
于 2016-04-06T19:49:27.880 回答
0

好吧,经过更多的故障排除后,我发现错误是由于我忘记包含而引起的

add esp, 4

在 _multiply_fact 过程中我调用 _factorial 之后

于 2016-04-06T06:12:48.447 回答