首先,我们先了解一下,什么是函数的调用过程?
函数的调用过程,顾名思义,即每一次函数调用都是一个过程。这个过程要为函数开辟栈空间,用于本次函数的调用中临时变量的保存,现场保护。这块栈空间称为函数栈帧。它存放为运行函数而分配的局部变量、函数参数、返回数据、返回地址等
从而,得到我们本篇博客的重点:栈帧
在接下来的描述中,我们将在VC6.0编译器32位环境下进行调试与观察,以此谈论栈帧的创建和销毁,那么首先写一段代码:
#include
int Add(int x, int y)
{
int z = 0;
z = x + y;
return z;
}
int main()
{
int a = 10;
int b = 20;
int c = 0;
c = Add(a, b);
printf("c = %d\n", c );
return 0;
}
在本代码中,调用了函数Add(),当调用函数Add()时,向内存申请一块大的内存空间,这块内存空间是在栈上创建,在接下来的描述中假设空间是向上使用的,栈是由高地址向低地址使用的,又因为栈上满足先进后出,当我们需要在开辟一块内存空间时,我们是在栈顶开辟,此时,我们需要了解两个用来维护栈帧的寄存器:
· ebp 存放了指向函数栈帧栈底的地址
· esp 存放了指向函数栈帧栈顶的地址
接下来,我们调用堆栈会发现main函数在_tmainCRTStartup函数中被调用,而_tmainCRTStartup函数是在mainCRTStartup被调用。要研究函数调用过程,从main函数的地方开始,要展开main函数的调用就得为main函数创建栈帧,接下来,我们先看一下main函数的反汇编,再逐句解释一下:
在整个程序开始之前,先为mainCRTStartup()开辟内存空间,如图:
看反汇编第一句 push ebp 意为 把ebp压栈,即在栈顶放一个元素,需要再申请一块空间,此时esp向上移动,维护新的栈顶,如图(1),我们可查看内存观察是否是这样的,如图(2)。
(1)
(2)
看反汇编第二句 mov ebp,esp 意为使esp的值赋给ebp,产生新的ebp,ebp将向上移动到现在esp的位置,如图:
看反汇编第三句 sub esp,4Ch 意为给esp减去一个十六进制数字4Ch,产生新的esp,如图:
看反汇编第四五六句 (push ebx) (push esi) (push edi)
看第七句 lea edi,[ebp-4Ch] 中lea指加载有效地址,则整句的意为把[ebp-4Ch]放入edi中
看第十句 rep stos dword ptr [edi] 中rep意为重复,stos意为存储,dword意为双字
则第七句到第十句的意为重复拷贝或者存储一个双字,从edi开始拷贝,拷贝次数由ecx决定
mov dword ptr [ebp-4],0Ah 意为 0A 放入[ebp-4],故以下三句可解释为如图所示:
在接下来的描述中,将不再如此详细的解释每一句的意思,总结一下:
call 指令后按 F11 跳转到:
再按 F11 就进入了Add函数的执行代码处
3、函数返回部分