您的位置:首页 > 其它

函数的调用过程(函数调用栈帧的创建)

2018-02-08 18:13 369 查看

函数的调用过程:

我们知道每一次函数调用都是一个过程,这个过程我们通常称之为:函数的调用过程。
而这个过程要为函数开辟栈空间,用于本次函数调用中临时变量的保存,现场保护,这块栈空间我们称之为函数栈帧。
而函数栈帧的维护需要两个寄存器:esp和ebp,在调用过程中,这两个寄存器存放了维护这个栈的栈顶和栈底指针。
举个例子:
调用main函数,我们为main函数分配栈帧空间,那么栈帧的维护如下图:



注意:ebp永远指向栈底    esp永远指向栈顶
下面通过一个简单的代码详细了解下函数的调用过程:
#include <stdio.h>
int Add(int x, int y)
{
int z = 0;
z = x + y;
return z;
}
int main()
{
int a = 10;
int b = 20;
int ret = Add(a, b);
printf("%d\n", ret);
return 0;
}
注意:在栈空间的使用是由高地址向低地址;
1.我们先从main函数的地方开始,要展开main函数的调用就必须为main函数创建栈帧,下面看看main函数栈帧的创建:
注:在调用main函数之前会先调用一个__tmainCRTStartup()函数
首先将ebp(__tmainCRTStartup())压栈处理(方便函数返回之后的现场恢复),然后使esp的值赋给ebp,产生新的ebp(为main函数开辟空间做准备),给esp减去一个16进制数字4ch,产生新的esp,此时,已为main函数开辟好空间。在该栈空间的栈顶进行压栈,分别压进去三个寄存器,分别是ebx,esi,edi(esp向上移动,指向新的栈顶),把栈帧预开辟的空间全部初始化成0xcccccccc,接下来处理局部变量a,b的创建。
2.接下来是Add函数的调用:
先进行传参,参数压栈,先压b,再压a(利用eax寄存器),call指令的调用,先要压栈call指令下一条指令的地址(保护现场,函数需要返回),然后跳转到Add函数的地方。
将ebp(main函数)进行压栈,将esp的值赋值给ebp,给esp减去一个十六进制的数字44h,产生新的esp,此时为Add函数预开辟好栈空间,把预开辟的栈空间全部初始化成0xcccccccc,创建变量Z,获取形参a和b相加,将结果存储到z中,再次将结果存储到eax寄存器中,通过寄存器带回函数的返回值。
3.函数的返回部分:
三次pop出栈,使得esp(Add函数)向下移动,将ebp的值赋值给esp,出栈,将出栈的内容内容保存到ebp中,回到main函数的栈帧,接下来利用ret指令将程序跳转到call指令下一条指令的地址处(ret指令会使得出栈一次,并将出栈的内容当作地址,将程序执行跳转到该地址处)。
附:
1.把运算结果放到z中,但是z不能返回给main(),所以又将z的值存到eax中,由eax带回。
2.在函数调用时实参和形参并不在同一个空间,而函数调用会直接去拿形参,对实参不会有影响。
3.保存上一个函数的ebp,是因为在函数调用完之后它需要返回上一个函数的栈底
4.函数调用完之后,应返回call指令的下一条指令继续执行程序。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: