c++循环执行一个函数_C|从流程图和汇编代码清晰看透递归函数代码的执行流程

先看下面的简单实例:

#include int counts(int n){printf("call: %d",n);if(n==0)return n;else{counts(n-1);printf("back: %d",n);return n;}}void main(){printf("%d",counts(3));getchar();}

运行结果:

call: 3call: 2call: 1call: 0back: 1back: 2back: 3

关键是明白其代码调用、回归的流程,以及其参数值是如何迭代的。

基本的流程如下:

253ad8c6725f7d2edc3767ca2fe460dc.png

下面从汇编的层面分析:

首先是主函数调用递归函数开始:

18:       printf("%d",counts(3));0040D7B8   push        30040D7BA   call        @ILT+0(counts) (00401005)0040D7BF   add         esp,4

call指令会完成两个动作,先是将call指令的下一条指令的地址0040D7BF压栈(push),以便返回;然后是执行一个jmp指令:

00401005   jmp         counts (00401020)

跳转到函数开始处00401020,先整体看一下函数的汇编代码:

3:    int counts(int n)4:    {00401020   push        ebp00401021   mov         ebp,esp00401023   sub         esp,40h00401026   push        ebx00401027   push        esi00401028   push        edi00401029   lea         edi,[ebp-40h]0040102C   mov         ecx,10h00401031   mov         eax,0CCCCCCCCh00401036   rep stos    dword ptr [edi]5:        printf("call: %d",n);00401038   mov         eax,dword ptr [ebp+8]0040103B   push        eax0040103C   push        offset string "call: %d" (00422028)00401041   call        printf (00401110)00401046   add         esp,86:        if(n==0)00401049   cmp         dword ptr [ebp+8],00040104D   jne         counts+34h (00401054)7:            return n;0040104F   mov         eax,dword ptr [ebp+8]00401052   jmp         counts+57h (00401077)8:        else9:        {10:           counts(n-1);00401054   mov         ecx,dword ptr [ebp+8]00401057   sub         ecx,10040105A   push        ecx0040105B   call        @ILT+0(counts) (00401005)00401060   add         esp,411:           printf("back: %d",n);00401063   mov         edx,dword ptr [ebp+8]00401066   push        edx00401067   push        offset string "back: %d" (0042201c)0040106C   call        printf (00401110)00401071   add         esp,812:           return n;00401074   mov         eax,dword ptr [ebp+8]13:       }14:   }00401077   pop         edi00401078   pop         esi00401079   pop         ebx0040107A   add         esp,40h0040107D   cmp         ebp,esp0040107F   call        __chkesp (00401190)00401084   mov         esp,ebp00401086   pop         ebp00401087   ret

然后分开来分析:

3:    int counts(int n)4:    {00401020   push        ebp00401021   mov         ebp,esp00401023   sub         esp,40h00401026   push        ebx00401027   push        esi00401028   push        edi00401029   lea         edi,[ebp-40h]0040102C   mov         ecx,10h00401031   mov         eax,0CCCCCCCCh00401036   rep stos    dword ptr [edi]

进入函数后,先压栈底指针,抬高栈底指针,提升栈顶指针,压寄存器,初始化这一段栈帧空间(debug模式)。

通常是一段这样的操作:

(地址值仅供参考)

3455f3fb4a5a7598ac40eb46ef13e35e.png

继续下面的汇编代码:

5:        printf("call: %d",n);00401038   mov         eax,dword ptr [ebp+8]0040103B   push        eax0040103C   push        offset string "call: %d" (00422028)00401041   call        printf (00401110)00401046   add         esp,86:        if(n==0)00401049   cmp         dword ptr [ebp+8],00040104D   jne         counts+34h (00401054)7:            return n;0040104F   mov         eax,dword ptr [ebp+8]00401052   jmp         counts+57h (00401077)8:        else9:        {10:           counts(n-1);00401054   mov         ecx,dword ptr [ebp+8]00401057   sub         ecx,10040105A   push        ecx0040105B   call        @ILT+0(counts) (00401005)00401060   add         esp,4

上面的cmp,在CPU内部是做一个减法,修改标志寄存器的值,根据标志寄存器的值形成跳转。

参数n减1后压栈。

递归调用函数,call指令会将地址00401060压栈,跳转到00401005。

循环上面的操作,继续抬高堆栈,直到参数值n==0:

00401049   cmp         dword ptr [ebp+8],00040104D   jne         counts+34h (00401054)7:            return n;0040104F   mov         eax,dword ptr [ebp+8]00401052   jmp         counts+57h (00401077)

返回:

00401077   pop         edi00401078   pop         esi00401079   pop         ebx0040107A   add         esp,40h0040107D   cmp         ebp,esp0040107F   call        __chkesp (00401190)00401084   mov         esp,ebp00401086   pop         ebp00401087   ret

C代码的返回,在汇编层面还有一些代码要生成并被执行,就是堆栈平衡,

然后是ret,与call相对应,ret指令也是执行两个动作,pop出在call时压进栈的返回地址,执行jmp命令返回:

00401060   add         esp,411:           printf("back: %d",n);00401063   mov         edx,dword ptr [ebp+8]00401066   push        edx00401067   push        offset string "back: %d" (0042201c)0040106C   call        printf (00401110)00401071   add         esp,812:           return n;00401074   mov         eax,dword ptr [ebp+8]13:       }14:   }00401077   pop         edi00401078   pop         esi00401079   pop         ebx0040107A   add         esp,40h0040107D   cmp         ebp,esp0040107F   call        __chkesp (00401190)00401084   mov         esp,ebp00401086   pop         ebp00401087   ret

引用递归调用的函数参数,将返回值压到寄存器eax,堆栈平衡,再ret返回。

循环上面的操作3次,ret到最首先调用的下一个语句的地址0040D7BF:

0040D7BF   add         esp,40040D7C2   push        eax0040D7C3   push        offset string "%d" (00422034)0040D7C8   call        printf (00401110)0040D7CD   add         esp,819:       getchar();

到此,递归调用完成。

存放堆栈指针的寄存器ebp、esp的值回归到原处。

-End-

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值