Recursion 如何使用堆栈和递归在汇编中查找斐波那契序列的第n个元素
我试图在汇编(英特尔x86)中编写经典的fib(n)函数,该函数返回Fibonacci序列的第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
.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