# 通过一道数据结构题谈递归(C和汇编)

### 递归和迭代

• 递归
``````long long factorial_recurse (int n)
{
if (n > 1) {
return n * factorial_recurse(n-1);
}
if (n == 1) {
return 1;
}
return -1; //should never go here
}
``````
• 迭代
``````long long factorial_iteration (int n)
{
long long res = 1;
while (n) {
res *= n;
n--;
}
return res;
}
``````

### 解题

``````f0: return;
=>  nothing

f1: print 1;  //1
f0;       //nothing
f0;       //nothing
=>  1

f2: print 2  //2
f1;      //1
f1;      //1
=> 2, 1, 1

f3: print 3  //3
f2;      //2, 1, 1
f2;      //2, 1, 1
=>  3, 2, 1, 1, 2, 1, 1

f4: print 4  //4
f3;      //3, 2, 1, 1, 2, 1, 1
f3;      //3, 2, 1, 1, 2, 1, 1
=>  4, 3, 2, 1, 1, 2, 1, 1, 3, 2, 1, 1, 2, 1, 1
``````

### 分析

• 递归与函数地址压栈和出栈有关，后文统一用push，pop代替。
• 取得任意一条指令的地址后，在执行该指令前，都会被送往CS:IP寄存器，以标记当前正在执行的指令的地址。
• 执行一个函数前，先从CS:IP寄存器中取出当前指令地址A，把A的下一条指令地址B，即函数断口地址push压栈，函数执行完，pop出断口地址B，把B给CS:IP，继续后续执行。
• 所谓断口地址，就是当前汇编指令之后的下一条指令的地址，此处可以理解为函数执行完后，后续程序指令的地址。

``````void fff(int k)
{
if (k>0) {
printf("before push k: %d\\\\n", k);
fff(k-1);
printf("after  pop  k: %d\\\\n", k);
}
}
``````

``````before push k: 4
before push k: 3
before push k: 2
before push k: 1
after  pop  k: 1
after  pop  k: 2
after  pop  k: 3
after  pop  k: 4
``````

Recurse Log

### 深入底层

``````Recurse_Test fff:
0x100000dc0 <+0>:  pushq  %rbp
0x100000dc1 <+1>:  movq   %rsp, %rbp
0x100000dc4 <+4>:  subq   \$0x10, %rsp
0x100000dc8 <+8>:  movl   %edi, -0x4(%rbp)
0x100000dcb <+11>: cmpl   \$0x0, -0x4(%rbp)
0x100000dcf <+15>: jle    0x100000de2               ; <+34> at main.c:71
->  0x100000dd5 <+21>: movl   -0x4(%rbp), %eax
0x100000dd8 <+24>: subl   \$0x1, %eax
0x100000ddb <+27>: movl   %eax, %edi
0x100000ddd <+29>: callq  0x100000dc0               ; <+0> at main.c:65
0x100000de2 <+34>: addq   \$0x10, %rsp
0x100000de6 <+38>: popq   %rbp
0x100000de7 <+39>: retq
0x100000de8 <+40>: nopl   (%rax,%rax)
``````

KimiTalk