您的位置:首页 > 其它

函数调用过程(栈帧)

2018-03-27 18:23 295 查看
众所周知,程序每调用一个函数,系统都会为其开辟一块空间,当它返回时,才收回这块空间。(程序崩溃有一部分原因就是因为无限制的调用函数,却没有及时返回,导致内存空间不够。)

为了更好的维护这一块空间(通常称为栈空间),我们需要了解两个寄存器,一个为 esp (指向栈顶的指针),一个为 ebp (指向栈底的指针)。栈空间的地址是从高地址向低地址使用的,即先使用高地址的空间,再使用低地址的空间。

main()函数也是函数,它也有返回值,“return 0;”,所以main函数也被其他函数调用。用过调用堆栈,可以查到,调用main()函数的是 __tmainCRTStartup() 函数; __tmainCRTStartup() 函数也被 mainCRTStartup() 函数调用。



每个程序开始运行的时候,都会先调用 mainCRTStartup() 函数和 __tmainCRTStartup() 函数,然后才调用 main() 函数,执行程序所写的代码。当然这都是编译器为我们做的事情,我们只需要了解一下,就可以了。
我们用一段比较简单的代码来进行演示:#include<stdio.h>

int Sum(int x, int y)
{
int z = 0;
z = x + y;
return z;
}

int main()
{
int a = 10;
int b = 20;
int ret = 0;
ret = Sum(a, b);
printf("%d\n", ret);
return 0;
}该程序实现了用调用函数求两个数相加之和。我们按F11运行程序,右键进入反汇编:


 
可以看到,在进入main()函数的时候,并不是先创建整形变量a,而是执行 push  ebp,即将现在ebp所指向的数据压入栈顶。现在ebp所指的是 __tmainCRTStartup() 函数的栈底,所以这句执行的结果就是保存 __tmainCRTStartup() 函数的栈底,这是为了当main()函数执行完之后返回用的。    
下一句 mov ebp,esp,即把ebp指针指向esp所指的位置。即现在ebp指向的也是目前栈顶空间地址。
sub esp,0E4h:sub是汇编指令的减法,这条语句意思是esp指针减去0E4h所得结果重新赋给esp,即esp指针向低地址移动了0E4h大小的空间,也就是目前为止,系统为main()函数开辟了0E4h大小的空间。(并不是所有的main()函数开辟空间的大小都为0E4h)。
下面执行三次push压栈指令,把三个寄存器压入栈顶,这三个寄存器会分别记录不同的信息,以便更好地维护这片空间。
再下一条指令lea的意思是加载有效地址,也就是把开辟的这0E4h大小空间的低地址处记录下来,放到edi寄存器里。
下面两条mov指令,是给两个寄存器赋值,把39h赋给ecx,把0CCCCCCCCh赋给eax。这两条指令都是为下边这条指令做铺垫的。
rep stos:重复存储,从edi开始,每次存储四个字节,赋值ecx次,赋值内容是eax。也就是把0E4h空间里面全部初始化为随机值。
到目前为止,上面所做的都是函数调用所产生的的系统开销。



后面就是创建变量a,b,ret。之后在进入函数之前,就会把a,b的形参放到寄存器里并且压入栈顶。
在call指令执行的时候,会把当前执行指令的下一条指令的地址记下来,并入栈保存;然后用目标函数的地址覆盖当前eip寄存器,达到跳转功能。



进入Sum函数后,前十条指令和main()函数并无太大区别,只是开辟的空间大小不同而已,都是执行相同的操作。然后再寄存器里面进行+操作,并把结果送到变量z里面。当函数结束,返回的时候,把需要返回的值,放到eax寄存器里面,再把创建函数的,压到栈顶的三个寄存器依次弹出。
mov esp,ebp:令栈顶指针指向栈底,即把这一块空间还回去了。此时,函数里面的局部变量已经不能再使用,里面的地址也不能再使用了。
pop ebp:把栈顶空间的东西弹出放到ebp里面,此时栈顶存放的是原来main()函数的ebp,所以执行完这条指令,ebp指针指向的便是main()函数的底部。    
下面是ret指令,ret指令会直接返回到call指令下一条指令的地方,并且把原来栈顶记录的地址弹出栈空间。



之后是销毁形参,并且像Sum()函数一样,一步步出栈,返回空间。



以上为我对函数调用过程中,栈帧的分析,如果有理解错误的地方,望指正。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  栈帧 函数调用