【问题标题】:Recursive Fibonacci in Assembly汇编中的递归斐波那契
【发布时间】:2011-08-02 18:24:56
【问题描述】:

我正在尝试在 Assembly 中实现递归斐波那契程序。但是,我的程序崩溃了,出现了一个未处理的异常,我似乎无法找出问题所在。我不怀疑这涉及我对堆栈的不当使用,但我似乎无法指出在哪里......

.386
.model Flat
public Fibonacci
include iosmacros.inc ;includes macros for outputting to the screen

.code
Fibonacci proc

MOV EAX, [EBP+8]
CMP EAX, 1
    JA Recurse
    MOV ECX, 1
    JMP exit

Recurse:
    DEC EAX
    MOV EDX, EAX
    PUSH EAX
    CALL Fibonacci
    ADD ESP, 4
    MOV EBX, ECX
    DEC EDX
    PUSH EDX
    CALL Fibonacci
    ADD ECX, EBX
exit:
ret
Fibonacci endp


.data


end

此外,我已将用于获取斐波那契值的数字推送到外部过程中的堆栈。有什么想法可能出在哪里吗?

【问题讨论】:

    标签: assembly fibonacci


    【解决方案1】:

    当你执行call时,下一个操作的地址被压入堆栈作为返回值。创建函数时,通常习惯于创建“堆栈框架”。此帧可用于打印调用堆栈,以及局部变量和参数的偏移量。框架是通过函数开头的两个操作创建的:

    push ebp
    mov ebp, esp
    

    在函数结束时,调用堆栈使用leave 删除,相当于这两个操作的相反操作。使用堆栈帧时,esp 的值存储到ebp,使其指向堆栈上称为帧基的位置。因为在这个地址之上,有ebp 的旧值和返回地址,你通常会使用[ebp+8] 获得第一个参数。但是,您没有设置堆栈帧。这意味着ebp 的旧值没有被压入堆栈,ebp 的当前值不能用于获取参数,因为你不知道它在哪里。因此,您应该使用[esp+4] 来获取您的论点。

    此外,习惯上将返回值放在eaxebx 中以供调用者使用。您的代码不遵循这些约定中的任何一个。此外,从技术上讲,函数不需要保留ecxedx,因此如果您希望保留它们,通常应该在调用另一个函数之前将它们推入堆栈。使用此代码,如果使用大于 2 的值调用 edxebx,将被覆盖,从而导致无效结果。

    这是一个完整列表,其中包括我提到的所有修复。我没有创建堆栈框架,因为它不是必需的,您的代码也没有。

    .386
    .model Flat
    public Fibonacci
    include iosmacros.inc ;includes macros for outputting to the screen
    
    .code
    Fibonacci proc
    
        MOV EAX, [ESP+4]
        CMP EAX, 1
        JA Recurse
        MOV EAX, 1 ; return value in eax
        JMP exit
    
    Recurse:
        PUSH EBX ; preserve value of ebx
        DEC EAX
        PUSH EAX
        CALL Fibonacci
        MOV EBX, EAX ; ebx is preserved by callee, so it is safe to use
        DEC [ESP] ; decrement the value already on the stack
        CALL Fibonacci
        ADD EAX, EBX ; return value in eax
        ADD ESP, 4 ; remove value from stack
        POP EBX ; restore old value of ebx
    exit:
    ret
    Fibonacci endp
    

    【讨论】:

    • 使用这个后,代码可以正常工作,但它仍然在某个地方崩溃。开始调试 - 谢谢!
    【解决方案2】:

    几个问题:

    • 如果您要在堆栈上传递参数,则您没有正确的(标准)函数入口,因此 EBP 指向错误的位置
    • 您没有在 edx 中正确保存参数值

    这就是我认为您想要的,假设您在堆栈上传递参数(最好为每条指令添加注释 明确你的想法):

    Fibonacci proc
      PUSH EBP          ; save previous frame pointer
      MOV  EBP, ESP     ; set current frame pointer
    
      MOV  EAX, [EBP+8] ; get argument N
      CMP  EAX, 1       ; N<=1?
      JA   Recurse      ; no, compute it recursively
      MOV  ECX, 1       ; yes, Fib(1)--> 1
      JMP  exit
    
    Recurse:
      DEC  EAX          ; = N-1
      MOV  EDX, EAX     ; = N-1
      PUSH EDX          ; save N-1
      PUSH EAX          ; set argument = N-1
      CALL Fibonacci    ; compute Fib(N-1) to ECX
      POP  EAX          ; pop N-1
      DEC  EAX          ; = N-2
      PUSH ECX          ; save Fib(N-1)
      PUSH EAX          ; set argument = N-2
      CALL Fibonacci    ; compute Fib(N-2) to ECX
      POP  EAX          ; = Fib(N-1)
      ADD  ECX, EAX     ; = Fib(N-1)+FIB(N-2)
    exit:
      MOV  ESP,EBP      ; reset stack to value at function entry 
      POP  EBP          ; restore caller's frame pointer
      RET               ; and return
    

    但您不必在堆栈上传递参数。使用寄存器效率更高:

    Fibonacci proc ; computes Fib(EAX) --> EAX; do not call with EAX==0
      CMP  EAX, 1      ; N<=1?
      JBE  exit        ; yes, we have the answer
      DEC  EAX         ; = N-1
      PUSH EAX         ; save N-1
      CALL Fibonacci   ; computing FIB(n-1)to EAX
      XCHG EAX,0[ESP]  ; swap FIB(n-1) for saved N-1 (You'll love this instruction, look it up in the Intel manual)
      DEC  EAX         ; = N-2
      CALL Fibonacci   ; computing FIB(N-2) to EAX
      POP  ECX         ; = FIB(N-1)
      ADD  EAX,ECX     ; = FIB(N-1)+FIB(N-2)
    exit:
      RET
    

    【讨论】:

    • 你忘了在最后弹出ebp。此代码将始终返回到堆栈上的某个位置。
    • @ughoavgfhw:是的,谢谢,已修复。只是为了证明自封的专家也搞砸了
    • 我已经进行了调整,但我的代码仍然无法正常工作。然而,这个回应似乎仍然是我正在寻找的答案。我相信我的问题现在在其他地方。至于使用寄存器,我最初尝试过,但似乎没有奏效。也许我会再试一次。谢谢!
    • @rar:我们已经发现专家无法正确编码:-} 使用调试器并单步执行说明。您很快就能找出问题所在,而且经验是无价的。
    • @rar:你确实选择了我根据 ughoavgfw(哇)的观察制作的补丁?
    【解决方案3】:

    首先,您使用的是 EBP 的 8 堆栈偏移量,为什么?你不是说ESP吗?正常调用只使用一个 32 位单元,因此您的 arg 应位于偏移量 4。我相当确定存在其他问题,但您可以从那开始。


    您可能应该编写一些伪代码,以便您和我们可以看到您要完成的工作。
    如果你想作弊,谷歌搜索“nasm recursive fibonacci”会带你进入一个工作程序。但是,如果您自己解决它,您将成为一个更好的程序员。

    【讨论】:

      猜你喜欢
      • 2012-11-06
      • 2013-10-13
      • 2021-07-18
      • 2014-06-24
      • 1970-01-01
      • 1970-01-01
      • 2012-03-25
      • 2020-08-11
      • 2010-12-03
      相关资源
      最近更新 更多