3.汇编代码分析
  汇编出的代码,多了很多辅助信息,为了能够更好地看清主干,我们删减一下:
1 g:
2     pushl    %ebp            //保存现场,将父函数的栈底寄存器存入当前程序栈中
3     movl    %esp, %ebp      //构建当前函数堆栈
4     movl    8(%ebp), %eax   //从父函数堆栈中取得参数,存入ax寄存器
5     addl    $3, %eax        //完成+3操作
6     popl    %ebp            //恢复原父函数堆栈
7     ret                     //pop出原EIP地址,恢复执行
8 f:
9     pushl    %ebp            //保存现场,将父函数的栈底寄存器存入当前程序栈中
10     movl    %esp, %ebp      //构建当前函数堆栈
11     subl    $4, %esp        //栈顶加一,用以储存变量传递给g函数
12     movl    8(%ebp), %eax   //取得参数
13     movl    %eax, (%esp)    //将参数传入变量位置
14     call    g               //调用g
15     leave                   //清楚局部变量空间
16     ret                     //返回
17 main:
18     pushl    %ebp
19     movl    %esp, %ebp
20     subl    $4, %esp        //空出局部变量空间
21     movl    $8, (%esp)      //为变量赋值
22     call    f               //调用f
23     addl    $1, %eax        //完成+1操作
24     leave                   //清理局部变量
25     ret                //返回
  我们对f函数进行详细的分析:
  1. 首先进行enter指令:

  此时,ebp当前所指向的位置存入栈顶,并且将ebp重定向指向esp:
  2.栈顶加一并存入变量值:

  3.调用g

  4.从g返回后,返回值储存在AX寄存器中,不用操作,调用leave,清理变量

  5.后ret,同时EIP被读出恢复到原位置继续执行,返回值在AX中传递给调用函数

  3.个人的一点感悟:
  程序的调用是这样嵌套的执行下去,每个函数都有自己的堆栈用以储存当前变量以及环境值,并通过将父函数的EBP放入栈底用以恢复环境。
  同时EIP存入父栈栈顶,便于恢复到原节点处继续执行。
  这样,可以有规律的一直嵌套下去。
  如果使用递归函数,是一个码堆栈的过程,知道顶部的堆栈返回,函数像多米诺骨牌一样收回所有的堆栈。
  这也是递归函数占用空间比较多的原因之一。如果没有很好地退出机制,有可能内存溢出。