Recursion 如何使用堆栈和递归在汇编中查找斐波那契序列的第n个元素

Recursion 如何使用堆栈和递归在汇编中查找斐波那契序列的第n个元素,recursion,assembly,x86,stack,fibonacci,Recursion,Assembly,X86,Stack,Fibonacci,我试图在汇编(英特尔x86)中编写经典的fib(n)函数,该函数返回Fibonacci序列的第n个元素 我已经很容易地编写了迭代版本,但是我在尝试使用递归操作时遇到了困难。这就是我所尝试的: .intel_syntax noprefix .text # unsigned fib(unsigned) .globl fib fib_n: enter 0, 0 mov eax, 0 # n = 0 or n = 1 cmp edi, 1 jbe end

我试图在汇编(英特尔x86)中编写经典的fib(n)函数,该函数返回Fibonacci序列的第n个元素

我已经很容易地编写了迭代版本,但是我在尝试使用递归操作时遇到了困难。这就是我所尝试的:

.intel_syntax noprefix

.text

# unsigned fib(unsigned)
.globl fib

fib_n:
    enter 0, 0

    mov eax, 0

    # n = 0 or n = 1
    cmp edi, 1
    jbe end

    mov eax, 1

    # n == 2
    cmp edi, 2
    je end

    # n > 2, entering recursion

    # fib(n-1)
    push rdi
    dec edi

    call fib_n
    mov r8d, eax 

    pop rdi

    # fib(n-2)
    push rdi
    sub edi, 2

    call fib_n
    mov r9d, eax

    pop rdi

    # fib(n) = fib(n-1) + fib(n-2)
    mov eax, r8d
    add eax, r9d

end:
    leave
    ret
我希望标记为#fib(n-1)和#fib(n-1)的调用将本地eax结果存储在r8d和r9d寄存器中,然后添加它们并通过eax返回,但这是我作为输出得到的结果:

n=1(第一个el):0(工作正常)
n=2:1(工作正常)
n=3:1(工作正常)

(错误的结果)
n=4:2
n=5:2

n=6:3
n=7:3
...

我还尝试将rax和rdi推到堆栈中,但仍然得到错误的结果。我错过了什么

我错过了什么

寄存器
r8d
不会在递归调用中保留

您不需要
输入
离开
。未推送任何参数。
将索引
rdi
保存在fib\n的条目中更容易

编辑

以下版本的代码包含和的注释。
这段代码现在删除了5个基本情况,并且更加干净

fib_n:
    cmp     edi, 5         ; n
    jb      .LT5           ; Base-cases
    push    rdi            ; (1) Preserve n
    dec     edi            ; n-1
    call    fib_n          ; -> EAX
    push    rax            ; (2) Preserve intermediate result!
    dec     edi            ; n-2
    call    fib_n          ; -> EAX
    pop     rdi            ; (2) Restore intermediate result
    add     eax, edi       ; fib(n) = fib(n-2) + fib(n-1)
    pop     rdi            ; (1) Restore n
    ret
.LT5:
    mov     eax, edi       ; n < 5
    cmp     edi, 2
    cmc
    sbb     eax, 0         ; fib(0) = 0, fib(1) = 1
    ret                    ; fib(2) = 1, fib(3) = 2, fib(4) = 3
fib\n:
cmp-edi,5;N
jb.LT5;基本情况
推动rdi;(1) 保存
dec-edi;n-1
呼叫fib\n;->EAX
推拉;(2) 保留中间结果!
dec-edi;n-2
呼叫fib\n;->EAX
流行性rdi;(2) 恢复中间结果
添加eax、edi;fib(n)=fib(n-2)+fib(n-1)
流行性rdi;(1) 恢复
ret
.LT5:
mov eax、edi;n<5
cmp edi,2
cmc
sbb-eax,0;fib(0)=0,fib(1)=1
ret;fib(2)=1,fib(3)=2,fib(4)=3

我理解我的错误。但是,我不理解代码的最后两行:addeax,[rsp]add rsp,8在eax中已经有fib(n-2)的结果,但是我在哪里使用了被推送到堆栈的保存的fib(n-1)值(即,我不理解rsp如何指出该值)。另外,当函数只有一个rdi寄存器使用的参数时,为什么需要删除8个字节而不是4个字节?编辑:我试图理解堆栈在指令执行时的样子:添加rsp,8。我猜[rsp]=rax(因为rax上次被推到堆栈中),[rsp+4]=rdi(这将在结束标签中显式弹出),但是[rsp+8]地址处是什么,这个菜单如何递归?在数学中,fib(0)=0,fib(1)=1,fib(2)=1,fib(3)=2,fib(4)=3@alexein777
[rsp]=rax
[rsp+8]=rdi
,[rsp+16]=返回地址`64位使用8字节推送!第一个用
add rsp删除,8
,第二个用
pop rdi
删除,第三个用
ret
@alexein777删除,使用
pop rax
肯定会将
rsp
提高8,但它也会破坏我们在
EAX
寄存器中准备好的结果!有关此问题的另一种更简洁的编码方法,请参阅我编辑的答案。注意,对于n的大值,这可能需要相当长的时间,因为呼叫总数将为2*fib(n)-1。32位无符号整数的最大值为fib(47)=2971215073,因此调用数将为5942430145(59.4亿)。
fib_n:
    cmp     edi, 5         ; n
    jb      .LT5           ; Base-cases
    push    rdi            ; (1) Preserve n
    dec     edi            ; n-1
    call    fib_n          ; -> EAX
    push    rax            ; (2) Preserve intermediate result!
    dec     edi            ; n-2
    call    fib_n          ; -> EAX
    pop     rdi            ; (2) Restore intermediate result
    add     eax, edi       ; fib(n) = fib(n-2) + fib(n-1)
    pop     rdi            ; (1) Restore n
    ret
.LT5:
    mov     eax, edi       ; n < 5
    cmp     edi, 2
    cmc
    sbb     eax, 0         ; fib(0) = 0, fib(1) = 1
    ret                    ; fib(2) = 1, fib(3) = 2, fib(4) = 3