栈帧的结构
倘若我们要想搞清楚过程的实现,就必须先知道栈帧的结构是如何构成的。栈帧其实可以认为是程序栈的一段,而程序栈又是存储器的一段,因此栈帧说到底还是存储器的一段。那么既然是一段,肯定有两个端点,这个不需要LZ再普及了吧。
这两个端点其实就是两个地址,一个标识着起始地址,一个标识着结束地址,而这两个地址,则分别存储在固定的寄存器当中,即起始地址存在%ebp寄存器当中,结束地址存在%esp寄存器当中。至于为什么要存在这两个寄存器当中,就像程序的下一条指令地址为什么存在PC当中一样,是毫无意义的问题,就是这样规定的,没有为什么。
起始地址和结束地址还有另外的名字,起始地址通常称为帧指针,结束地址通常称为栈指针(也就是栈顶的地址)。因此,我们就把过程的存储器内存使用区域称为栈帧。这下我们就了解了栈帧的来历以及它们的命名习惯和存储惯例,接下来是LZ画的一幅图,它揭示了栈帧在存储器当中的位置。
这个图基本上已经包括了程序栈的构成,它由一系列栈帧构成,这些栈帧每一个都对应一个过程,而且每一个帧指针+4的位置都存储着函数的返回地址,每一个帧指针指向的存储器位置当中都备份着调用者的帧指针。各位需要知道的是,每一个栈帧都建立在调用者的下方(也就是地址递减的方向),当被调用者执行完毕时,这一段栈帧会被释放。还有一点很重要的是,%ebp和%esp的值指示着栈帧的两端,而栈指针会在运行时移动,所以大部分时候,在访问存储器的时候会基于帧指针访问,因为在一直移动的栈指针无法根据偏移量准确的定位一个存储器位置。
还有一点比较重要的内容,就是栈帧当中内存的分配和释放。由于栈帧是向地址递减的方向延伸,因此如果我们将栈指针减去一定的值,就相当于给栈帧分配了一定空间的内存。这个理解起来很简单,因为在栈指针向下移动以后(也就是变小了),帧指针和栈指针中间的区域会变长,这就是给栈帧分配了更多的内存。相反,如果将栈指针加上一定的值,也就是向上移动,那么就相当于压缩了栈帧的长度,也就是说内存被释放了。需要注意的是,上面的一切内容,都基于一个前提,那就是帧指针在过程调用当中是不会移动的。
过程的实现
过程虽然很好,但想要实现过程,还是存在一定难度的,尽管现在看来它并不困难。它实现的难度主要就在于数据如何在调用者和被调用者之间传递,以及在被调用者当中局部变量内存的分配以及释放。
不过天大的难题都难不倒那群计算机界的大神们,他们找出了一种方式,可以简单并有效的处理过程实现当中的难题。这一切似乎看起来十分偶然,但其实也是必然的。世间的很多规律都是客观存在的,只是它在等着我们去发现而已。
总的来说,过程实现当中,参数传递以及局部变量内存的分配和释放都是通过以上介绍的栈帧来实现的,大部分情况下,我们认为过程调用当中做了以下几个操作。
1、备份原来的帧指针,调整当前的帧指针到栈指针的位置,这个过程就是我们经常看到的如下两句汇编代码做的事情。