• 函数递归与栈的关系


    首先通过反汇编语言,我们来了解一下最简单的递归函数与栈之间的关系。

    如何获得反汇编语言,在visual studio 2008中,在debug环境下,在debug/windows/disassembly中可以查看反汇编之后的语言。现在我们看一下阶乘n!的实现

    其C语言实现代码如下

    [cpp] view plaincopy
     
    1. #include <stdio.h>  
    2. int factorial(int n);  
    3. int main(void)  
    4. {  
    5.     int fact;  
    6.     fact = factorial(4);  
    7.     printf("%d ",fact);  
    8.     return 0;  
    9. }  
    10. int factorial(int n)  
    11. {  
    12.     if (1 == n )  
    13.         return 1;  
    14.     return n * factorial(n - 1);  
    15.   
    16. }  


    其反汇编之后的语言如下

    主程序main

    [plain] view plaincopy
     
    1. int main(void)  
    2. {  
    3. 00DB1FD0  push        ebp    
    4. 00DB1FD1  mov         ebp,esp   
    5. 00DB1FD3  sub         esp,0CCh   
    6. 00DB1FD9  push        ebx    
    7. 00DB1FDA  push        esi    
    8. 00DB1FDB  push        edi    
    9. 00DB1FDC  lea         edi,[ebp-0CCh]   
    10. 00DB1FE2  mov         ecx,33h   
    11. 00DB1FE7  mov         eax,0CCCCCCCCh   
    12. 00DB1FEC  rep stos    dword ptr es:[edi]   
    13.     int fact;  
    14.     fact = factorial(4);  
    15. 00DB1FEE  push        4      
    16. 00DB1FF0  call        @ILT+475(_factorial) (0DB11E0h)   
    17. 00DB1FF5  add         esp,4   
    18. 00DB1FF8  mov         dword ptr [fact],eax   
    19.     printf("%d ",fact);  
    20. 00DB1FFB  mov         esi,esp   
    21. 00DB1FFD  mov         eax,dword ptr [fact]   
    22. 00DB2000  push        eax    
    23. 00DB2001  push        offset string "%d " (0DB5A38h)   
    24. 00DB2006  call        dword ptr [__imp__printf (0DB82BCh)]   
    25. 00DB200C  add         esp,8   
    26. 00DB200F  cmp         esi,esp   
    27. 00DB2011  call        @ILT+320(__RTC_CheckEsp) (0DB1145h)   
    28.     return 0;  


    其factorial函数的汇编如下

    [plain] view plaincopy
     
    1. int factorial(int n)  
    2. {  
    3. 00DB1AF0  push        ebp    
    4. 00DB1AF1  mov         ebp,esp   
    5. 00DB1AF3  sub         esp,0C0h   
    6. 00DB1AF9  push        ebx    
    7. 00DB1AFA  push        esi    
    8. 00DB1AFB  push        edi    
    9. 00DB1AFC  lea         edi,[ebp-0C0h]   
    10. 00DB1B02  mov         ecx,30h   
    11. 00DB1B07  mov         eax,0CCCCCCCCh   
    12. 00DB1B0C  rep stos    dword ptr es:[edi]   
    13.     if (1 == n )  
    14. 00DB1B0E  cmp         dword ptr [n],1   
    15. 00DB1B12  jne         factorial+2Bh (0DB1B1Bh)   
    16.         return 1;  
    17. 00DB1B14  mov         eax,1   
    18. 00DB1B19  jmp         factorial+3Eh (0DB1B2Eh)   
    19.     return n * factorial(n - 1);  
    20. 00DB1B1B  mov         eax,dword ptr [n]   
    21. 00DB1B1E  sub         eax,1   
    22. 00DB1B21  push        eax    
    23. 00DB1B22  call        @ILT+475(_factorial) (0DB11E0h)   
    24. 00DB1B27  add         esp,4   
    25. 00DB1B2A  imul        eax,dword ptr [n]   
    26.   
    27. }  
    28. 00DB1B2E  pop         edi    
    29. 00DB1B2F  pop         esi    
    30. 00DB1B30  pop         ebx    
    31. 00DB1B31  add         esp,0C0h   
    32. 00DB1B37  cmp         ebp,esp   
    33. 00DB1B39  call        @ILT+320(__RTC_CheckEsp) (0DB1145h)   
    34. 00DB1B3E  mov         esp,ebp   
    35. 00DB1B40  pop         ebp    
    36. 00DB1B41  ret           


    在整个汇编程序中,在

    [plain] view plaincopy
     
    1. call        @ILT+475(_factorial) (0DB11E0h)  

    之前的push 为参数的入栈。这儿是关键,其他的push我们可以认为是系统为了栈的平衡而进行的必要操作。

    在factorial的反汇编中,

    [plain] view plaincopy
     
    1. 00DB1B39  call        @ILT+320(__RTC_CheckEsp) (0DB1145h)  

    这句话是函数factorial调用自己本身,也就是递归。

    push eax;将每次入栈的参数保存到eax寄存器中,然后再入栈,这样在n != 1时,每次的参数都会入栈;

    [plain] view plaincopy
     
    1. 00DB1B2A  imul        eax,dword ptr [n]   

    这一步骤是为了进行相乘。在eax寄存器中保存相乘的值。

    其实在整个过程中,牵涉到函数调用中栈帧的一系列操作,http://blog.csdn.net/free2011/article/details/6868334这篇博客详细讲述了调用函数过程中栈帧的一系列操作。

    进行一个总结:

               函数递归是利用系统中栈进行操作的,通过对栈帧的一系列操作,从而实现递归。这个过程是由系统来完成的。

    在阶乘中,我们通过对factorial函数的参数入栈,然后通过栈帧的一系列操作,从而实现参数的出栈,进而完成阶乘这个动作。整个过程实际上就是一个栈的入栈和出栈问题。

    现在我们要通过自己定义一个栈来实现函数递归。

    [cpp] view plaincopy
     
    1. #include "stack.h"  
    2. #define  NumOfStack 10  
    3. int main(void)  
    4. {  
    5.     StackNode * pStackNode = NULL ;  
    6.     int NOfFact;  
    7.     int tmp = 1,Sum = 1;  
    8.     pStackNode = CreateStack(NumOfStack);  
    9.     printf("the number of Factorial ");  
    10.     scanf("%d",&NOfFact);  
    11.     while(NOfFact)  
    12.     {  
    13.         Push(pStackNode,NOfFact--);  
    14.     }  
    15.     while(pStackNode->top)  
    16.     {  
    17.         Pop(pStackNode,&tmp);  
    18.         Sum *= tmp;  
    19.     }  
    20.     printf("sum is %d ",Sum);  
    21.     return 0;  
    22. }  


    仅仅呈现主程序部分。在主程序中,我们首先对参数入栈,也就是对n 、n-1、...1入栈,然后再出栈进行操作。

    import org.junit.Test;
    
    /**
     * Created by yuchao on 2015/9/13.
     */
    public class digui {
        @Test
        public void  hello()
        {
            dg("递归初始",0);
        }
    
        public void dg(String dname,int i)
        {
            System.out.println("dg("+dname+","+i+")");
            i++;
            if(i<4) {
                dg("上层递归",i);
                dg("下层递归",i);
            }else {
                System.out.println("--");
            }
        }
    
    }


    dg(递归初始,0)
    dg(上层递归,1)
    dg(上层递归,2)
    dg(上层递归,3)
    --
    dg(下层递归,3)
    --
    dg(下层递归,2)
    dg(上层递归,3)
    --
    dg(下层递归,3)
    --
    dg(下层递归,1)
    dg(上层递归,2)
    dg(上层递归,3)
    --
    dg(下层递归,3)
    --
    dg(下层递归,2)
    dg(上层递归,3)
    --
    dg(下层递归,3)
    --

    Process finished with exit code 0

    这篇文章写的比较概括,我希望告诉大家的是,通过观看反汇编语言中关于阶乘的递归实现的运行过程及步骤,能够加深我们对于函数递归和栈的理解。虽然汇编语言有些难懂,但是通过阅读上面为大家推荐blog,相信大家都能够看懂。

     

  • 相关阅读:
    HDU-2262 Where is the canteen 概率DP,高斯消元
    HDU-4418 Time travel 概率DP,高斯消元
    无人驾驶相关数据集
    C++——编译器运行过程
    C++——Struct 和 Union区别
    常用linux指令
    无人驾驶——定位
    Ubuntu 没有 无线网 RTL8821ce 8111 8186
    无人驾驶之传感器融合算法
    LIN通讯
  • 原文地址:https://www.cnblogs.com/viewcozy/p/4805986.html
Copyright © 2020-2023  润新知