返回信息流#include <stdio.h>
void main()
{
int fun(int);
int a;
a=fun(4);
printf("%d",a);
}
int fun(int b)
{
if(b==0)
return 0;
else if(b==1)
return 1;
else
return (fun(b-1)+fun(b-2));
}
这是一条镜像帖。来源:北邮人论坛 / cpp / #74381同步于 2013/10/11
该镜像源已超过 30 天没有更新,可能在源站已被删除。
CPP机器人发帖
哪位大神能给仔细分析一下这个递归过程中栈的变化,详细的啊。
z843259180
2013/10/11镜像同步13 回复
订阅后,新回复会通过你的通知中心匿名送达。
9 条回复
【 在 z843259180 的大作中提到: 】
: #include <stdio.h>
: void main()
: {
: ...................
补充一下哈,递归的思想我理解,所以希望大神们不要空洞的说,最好能给出栈状态的变化过程。。
fun函数的最前面加上
printf("enter %d\n", b);
最后加上
printf("exit %d\n", b);
不久能看到栈变化了么
更直观一点,传递一个depth变量用来缩进
【 在 jokerlee 的大作中提到: 】
: fun函数的最前面加上
: printf("enter %d\n", b);
: 最后加上
: ...................
谢谢你哈,我试试
初学AMD64汇编,试着解释一下……如何操作栈,和CPU有关,和操作系统有关,和calling convention有关,和编译器如何优化有关。x86(32位)和x86_64上不一样,都是x86_64,Windows和Linux不一样。
如果是x86_64和Linux系统的话,因为c语言里的int仍然是32位的,所以第一个参数b会用edi寄存器传入,返回值是eax。rdi,rsi,rdx,rcx,r8,r9归被调用的函数随便用(caller save,也就是调用者有责任保存),其余的寄存器如果想用的话,push一下也可以用。AMD64 ABI并不强制每次调用开始都要保存rbp并创建一个帧。
所以递归调用之前,只要能够把上述6个caller-save的寄存器中用过的(应该只有那个参数b的值(rdi),不会用别的寄存器了吧)压到栈上,用call会帮你压上RIP,然后返回的时候恢复一下就可以了。所以每一级递归,栈上可以只有上一层的b的值、前一次递归调用的返回值,以及返回地址。
当然,上述只是一种编译方案。
【 在 nuanyangyang 的大作中提到: 】
: 初学AMD64汇编,试着解释一下……如何操作栈,和CPU有关,和操作系统有关,和calling convention有关,和编译器如何优化有关。x86(32位)和x86_64上不一样,都是x86_64,Windows和Linux不一样。
: 如果是x86_64和Linux系统的话,因为c语言里的int仍然是32位的,所以第一个参数b会用edi寄存器传入,返回值是eax。rdi,rsi,rdx,rcx,r8,r9归被调用的函数随便用(caller save,也就是调用者有责任保存),其余的寄存器如果想用的话,push一下也可以用。AMD64 ABI并不强制每次调用开始都要保存rbp并创建一个帧。
: 所以递归调用之前,只要能够把上述6个caller-save的寄存器中用过的(应该只有那个参数b的值(rdi),不会用别的寄存器了吧)压到栈上,用call会帮你压上RIP,然后返回的时候恢复一下就可以了。所以每一级递归,栈上可以只有上一层的b的值、前一次递归调用的返回值,以及返回地址。
: ...................
您说的这些我也看了一下,在一次函数调用的情况下我还能勉强搞清楚,可遇到这种稍微复杂一点的递归调用就混乱了。。每次返回的时候栈中的东西(包括一次调用的形参啦、下一条指令的地址啦、还有一些局部变量的存储空间啦、还有一些您提到的可能寄存器中的一些东西(没学过汇编不大了解。。。好像也不用纠结这些寄存器是吗))都要弹出来(当然仅是相对于这一次调用的东西)吗,还是咋弹出的。。表示混乱
以Win7 IntelX86为例:
int fun(int b)
{
if(b==0)
return 0;
else if(b==1)
return 1;
else
return (fun(b-1)+fun(b-2));
}
; in main
; a = fun(4);
mov [esp], 4
call _fun
mov [esp+20h+var_4], eax
; ----------------------------------------
; in fun
; init
push ebp ; 保留栈基址
mov ebp, esp
push ebx
sub esp, 0x14
; ----------------------------------------
cmp [ebp + 0x8], 0 ; if (b == 0)
jnz short loc_401348 ; 不等则跳
mov eax, 0
jmp short loc_401373 ; return 0;
; ----------------------------------------
loc_401348: ; from jnz short loc_401348
cmp [ebp + 0x8], 1 ; if (b == 1)
jnz short loc_401355 ; 不等则跳
mov eax, 1
jmp short loc_401373 ; return 1;
; ----------------------------------------
loc_401355: ; from jnz short loc_401355
mov eax, [ebp + 0x8]
dec eax ; b - 1
mov [esp], eax
call _fun ; 计算fun(b - 1),结果放入eax
mov ebx, eax ; 结果暂存进ebx
mov eax, [ebp + 0x8] ; 恢复eax = b
sub eax, 2 ; b - 2
mov [esp], eax
call _fun ; 计算fun(b - 2),结果放入eax
add eax, ebx ; 此时eax为fun(b - 2)的结果,直接加ebx即可
; ----------------------------------------
loc_401373:
add esp, 0x14 ; 恢复堆栈,结果保存在eax中
pop ebx
pop ebp
retn
【 在 z843259180 的大作中提到: 】
: #include <stdio.h>
: void main()
: {
: ...................
嗯。楼上言之有理。对于C的用户,只需要知道:每次调用,都会创建它的执行环境,包括局部变量(也包括参数)和当前指令;而且上一层调用的执行环境是保存起来的。毕竟高级语言是抽象的。打印一下看看就行了。
【 在 zxsword 的大作中提到: 】
: 直接加打印吧。
: 这个递归不用去了解底层=。=加了打印,一目了然那个先执行哪个后执行,楼主想要的就是这个吧。。。
加什么打印??在哪加??直接在fun()函数体最前面加printf("%d",b)吗??