汇编之递归

汇编之递归

这是一个经典的递归代码

用来累加3+2+1

#include<stdio.h>
int accumulation(int n){
	n&&(n+=accumulation(n-1));
	return n;
}
int main(){
	printf("%d\n",accumulation(3));
	return 0;
}

vc++反汇编查看后,看主要的几句:

printf("%d\n",accumulation(3)):
push  		3 
call  		@ILT+0(_accumulation) (00401005)
add   		esp,4
push  		eax
push  		offset string "%d\n" (0042201c)
call        printf (00401120)
add         esp,8

首先将3压入了堆栈,接下来调用accumulation函数,但并不是直接调用到了函数入口处。

@ILT+0(_accumulation):
00401005 E9 16 00 00 00       jmp         accumulation (00401020)

@ilt相当于一个跳板,记录了一些函数的入口位置,当一个程序中多次调用一个函数,都可以通过ilt进行跳转,这样的好处是当函数入口位置更改时,只要修改@ilt中的位置,有利于提高效率。

 n&&(n+=accumulation(n-1)):
 cmp         dword ptr [ebp+8],0
 je          accumulation+35h (00401055)
 mov         eax,dword ptr [ebp+8]
 sub         eax,1
 push        eax
 call        @ILT+0(_accumulation) (00401005)
 add         esp,4
 mov         ecx,dword ptr [ebp+8]
 add         ecx,eax
 mov         dword ptr [ebp+8],ecx

到了accumulation函数中首先当然是开辟栈空间等一系列常规操作,然后就到了最主要的部分。

第一步
  1. 将【ebp+8】的值与0进行比较,这个【ebp+8】实际上就是之前被压入栈中的3.比较发现不相等,继续往下走。
  2. eax=3
  3. eax=eax-1=2
  4. eax的值压入栈
  5. 调用自身

调用自己后又是一系列的开辟栈空间的常规操作,然后。

第二步
  1. 将【ebp+8】的值与0进行比较,这时【ebp+8】的值是之前被压入栈的eax的值2
  2. 把2赋给eax,eax=2
  3. eax=eax-1=1
  4. 将1压入栈
  5. 调用自身
第三步
  1. 将之前被压入栈的值1与0进行比较,不相等,继续往下进行
  2. eax=1
  3. eax=eax-1=0
  4. 将0压入栈
  5. 调用自身
第四步
  1. 0与0比较,相等,跳转到accumulation+35h (00401055)的位置,这个位置查看后发现就是return n的位置`。
 return n;
00401055   mov         eax,dword ptr [ebp+8]

以上这一系列操作完成后大概就是这个样子:
在这里插入图片描述

此时的eax是0,这个记住,之后要进行计算。

先看一下栈空间是什么样子:

push        ebp
mov         ebp,esp
sub         esp,40h
push        ebx
push        esi
push        edi
lea         edi,[ebp-40h]
mov         ecx,10h
mov         eax,0CCCCCCCCh
rep stos    dword ptr [edi]

以上是每次函数开始都会进行的常规操作,一波操作完后的栈空间大概长这个样子:
在这里插入图片描述
地址和上面的图一样,底下是高地址,上边是低地址。

 }
pop         edi
pop         esi
pop         ebx
add         esp,40h
cmp         ebp,esp
call        __chkesp (004010e0)
mov         esp,ebp
pop         ebp
ret

以上操作是逐步退出堆栈,栈空间清空的过程。一通pop操作后,edi,esi等寄存器一次退出,esp也退到了ebp指向的位置,pop ebp后,ebp的位置退到了旧的ebp的位置。
值得注意的是ret操作,这个很有灵性,ret操作主要分为两步:1.将当前esp指向的位置pop给eip。2.add esp,4。

那么当前esp指向什么位置呢?

从最后几步操作来看,mov esp,ebp,pop ebp后esp应该指向图中旧ebp下面的位置,那旧ebp下面是什么呢,在第一幅图中没画出来,实际在每一次call调用时,call函数都会首先把下一步操作的地址压入栈,即 add esp,4这一步的地址.
在这里插入图片描述
所以,ret后会执行add esp,4.

接下来就是真正的累加操作了
 add         esp,4
 mov         ecx,dword ptr [ebp+8]
 add         ecx,eax
 mov         dword ptr [ebp+8],ecx
 return n;
 mov         eax,dword ptr [ebp+8]
 ……
 ……
 ret

此时的ebp+8是指向之前被压入栈中的数字的,

第一次:
  1. dword ptr [ebp+8]指向的数字是1,ecx=1
  2. ecx=ecx+eax=1+0=1
  3. [ebp+8]=ecx=1
  4. eax=1
第二次:
  1. dword ptr [ebp+8]指向的数字是2,ecx=2
  2. ecx=ecx+eax=2+1=3
  3. [ebp+8]=ecx=3
  4. eax=3
第三次:
  1. dword ptr [ebp+8]指向的数字是3,ecx=3
  2. ecx=ecx+eax=3+3=6
  3. [ebp+8]=ecx=6
  4. eax=6

这是最后一回调用自身函数了,接下去的ret操作返回值就不是call accumulation的下一步了。而是返回到了main函数中call printf 函数的下一步。
而在printf函数运行机制中,eax寄存器中的值一般就是会被打印出来的值,所以最后得出结果6。
感觉递归就是 call在一层层向下嵌套,ret在一层层向上逃离的过程。
在这里插入图片描述

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值