【C语言】详解栈帧

来源:互联网 发布:js protocol buffer 编辑:程序博客网 时间:2024/04/29 11:10


今天我们将通过栈帧详解C程序中函数间的调用过程。

栈帧是什么?

    栈帧也叫过程活动记录,是编译器用来实现过程/函数调用的一种数据结构。从逻辑上讲,栈帧就是一个函数执行的环境:函数参数、函数的局部变量、函数执行完后返回到哪里等等。

首先应该明白,栈是从高地址向低地址延伸的。每个函数的每次调用,都有它自己独立的一个栈帧结构,这个栈帧中维持着所需要的各种信息。寄存器ebp指向当前的栈帧的底部(高地址),寄存器esp指向当前的栈帧的顶部(低地址)。但是系统并不是给每个栈帧创造一套独有的ebp和esp, 而是大家共用一套。那么他们是怎么公用的呢?

 

我们以如下一个简单的函数调用为例:

#include <stdio.h>#include <windows.h>int fun(int x, int y){int c = 0xcccccccc;return c;} int main(){int a = 0xaaaaaaaa;int b = 0xbbbbbbbb;int ret = fun(a, b);printf("you should running here!\n");return 0;}

 

我们需要知道main函数执行的整个过程。

注意:main函数也是函数,它是被start函数调用的。

注意;函数的栈帧被释放,并不是将其内容清空,而是做一个标记,说明此空间里面的数据无效了。

当又有新函数被调用形成新的栈帧时,只要直接覆盖这片空间就好了。


调用main函数:(如图1)

(1)将当前正在执行的指令的下一条指令的地址(PC)压入start函数栈中。

(1)start函数调用main函数,把ebp先压栈,用来保存start函数ebp栈底指针的位置。

(2)将start函数的esp的值赋给ebp,即就是让ebp指向esp的位置,也就是让ebp作为将要开辟的main函数栈帧的栈底。

(3)让esp的值减去某个值,让它指向另一个位置,这是在为main函数栈帧开辟空间。

(4)将main函数中定义的变量按照定义的先后顺序存入main栈帧中。






调用fun函数:(如图2)

(1)从右向左进行参数初始化,因为fun函数是main函数调用的,所以按初始化顺序将参数压入main函数的栈中。

(2)将当前正在执行的指令的下一条指令的地址(PC)压入main函数栈中。

(3)然后将main函数的ebp压栈,用来保存main函数ebp指针的位置。

(4)将main的esp的值赋给ebp,即就是让ebp指向esp的位置,也就是让ebp作为将要开辟的fun函数栈帧的栈底。

(5)让esp的值减去某个值,让它指向另一个位置,这是在为fun函数栈帧开辟空间。

(6)将fun函数中定义的变量按照定义的先后顺序存入fun栈帧中.






fun函数调用完毕:(如图3)

(1)将esp指向ebp的地址,使fun函数栈帧的esp栈顶指针重新恢复为main函数的栈顶指针。

(2)然后从main函数的栈顶弹出main函数的ebp (因为在调用fun函数之前保存了main函数的ebp),使得ebp重新指向main函数的栈底。

     这就完成了fun函数栈帧的释放,局部变量也跟着栈帧的释放被释放掉了。

     同时从main函数栈顶弹出esp当前指向的内容(下一条指令的地址),带着fun函数返回值,去找下一条指令继续执行。





返回main函数:(如图4)

(1)fun函数调用完毕,初始化参数也随之失效,通过移动esp站定指针将其移除。

(2)将fun函数返回值赋给ret.







main函数调用完毕:(如图5)

(1)将esp减去加上某个值,这就完成了main函数栈帧的释放,局部变量也跟着栈帧的释放被释放掉了。

(2)再将esp指向ebp的地址,使main函数栈帧的esp栈顶指针重新恢复为start函数的栈顶指针。

(3)然后弹出start函数的ebp (因为在调用main函数之前保存了start函数的ebp),使得ebp重新指向start函数的栈底。

  



最后我们再来总结一下,通过上述介绍我们不难看出,每个函数都有一个自己的栈帧,函数的调用伴随着栈帧的创建,函数的局部变量也同时在栈帧中定义,不断地调用函数就不断地创建新的栈帧,当函数调用完毕时,释放其栈帧,局部变量也同时被销毁。


1 0
原创粉丝点击