栈帧——函数调用,变量在内存中如何存取

来源:互联网 发布:淘宝买家修改评价链接 编辑:程序博客网 时间:2024/06/06 07:51

首先我们先要搞懂程序的地址空间是怎样的。



堆区和栈区相对生长,而我们的变量是存在栈区,接下来我们就要对程序运行时的栈区进行讨论。

用以下程序为例:

#include<stdio.h>#include<windows.h>int add(int a,int b){int z=a+b;return z;}int main(){int a=1;int b=1;int z=add(a,b);printf("%d\n",z);system("pause");return 0;}

我们通过转到汇编语言来更好的观察程序的每一步。

每进入一个函数我们就需要给这个函数开辟一个空间,而这个空间我们叫栈帧。

从地址00401060到00401076,这一段是给开辟的空间先进行初始化。

首先我们得了解一些寄存器:ebp叫做栈底,esp为栈顶,eip为存储下一条指令地址的寄存器。


然后我们程序再往下走,分别在栈底以4字节为单位,开辟空间分别保存a=1,b=1。

然后对a,b两个变量进行压栈。其中压栈是压从esp栈顶开始的。

00401086~0040108D的示意图如下:

(绿色块为main函数的栈帧)


然后我们来到了call命令处,继续按F11往下走。



其中call有如下两个功能:

1.将当前正在执行指令的下一条指令的地址,压入栈中。

2.jmp,跳转到指定函数。

然后我们进入了add函数,同样会给add这个函数开辟一个空间。

如图的蓝色块,称为add的栈帧。

通过call命令将当时的下一条指令的地址压入栈中(00401093),其目的是当add函数调用结束时,返回main。

00401020的意思是将main函数的ebp地址也压入栈中。

00401021~00401036,同样是给add函数开辟空间并初始化。

然后对z=a+b;进行操作。由示意图我们可以看到,我们用的形参a,b并不在add函数内部,而在main与add之间。

将相加的结果2保存在eax寄存器中。


程序结束后,还有一堆pop弹出栈的操作,00401044~0040104A。add函数的栈空间(栈帧)销毁。

ret的功能有2个:

1.弹出栈顶

2.弹出的值放入EIP中。






跳回主函数后,将eax寄存器中的值2继续存入main函数的栈空间,也就是得到add函数的返回值。

调用add函数的过程,到此已经全部结束。


总结:

1.我们可以看到形参是保存在栈帧与栈帧之间的。

2.形参的存储顺序与实参相反(先压栈的后出)。

3.临时变量的生命周期伴随着栈帧的开始而开始,结束而结束。

4.返回值以寄存器的形式返回。

阅读全文
0 0