一、先导知识
C/C++中内存分为3个区域:栈区、堆区、静态区
不同性质的变量存放在不同的内存区域中,下图是各种变量所在内存中的区域
本文所讲的函数栈帧的创建和销毁过程就是在栈区进行的
栈区存放变量的特点:先存高地址,再存低地址。
销毁变量的时候是先销毁低地址里面的变量,再销毁高地址里面的变量,如图
例如:变量1先创建,再创建变量2......创建变量8;但是销毁的时候,就是先销毁变量8......最后销毁变量1
本文中将用到的两个寄存器:ebp、esp,这两个指针中存放的是地址,用来维护函数栈帧
ebp是栈底指针、esp是栈顶指针,正在调用哪个函数,ebp、esp就维护哪个函数的函数栈帧
二、函数调用堆栈
在vs2013中来测试并观察main函数栈帧的创建过程
F10-->调试-->窗口-->调用堆栈 一直按F10直到程序结束,就会出现下面的场景
可以看到main函数是由626行的那个函数 __tmainCRTStartup函数调用的
而 __tmainCRTStartup 函数又是由466行的 mainCRTStartup 函数调用的,调用的顺序正是由栈顶往栈底
三、函数栈帧的创建
1.创建函数栈帧
有(二)中可知,VS2013中,main函数也是被其他函数调用的,每一次函数调用都要在栈区上分配空间,接下来就通过观察汇编代码来看观看函数栈帧的创建,假设mian函数中还有一个Add函数,来计算两个数的相加的和。按下F10后不要乱动,右击鼠标,转到反汇编(C语言对应的汇编代码)
这就是这段代码对应的汇编代码
由于main函数是由 __tmainCRTStartup函数调用的,所以在调用main函数前,main函数的函数栈帧已经创建好了
push:给栈顶放一个元素
push ebp 将ebp里面的值放在栈顶,同时因为esp指向的是栈顶,所以esp指针也‘上移’
如图
mov:将前面的值放到后面值的地方
mov ebp,esp,将ebp指针移动到esp指针的位置
sub:前面的值减去后面的值
sub esp,0E4h,指针esp减去0E4h(一个8进制的数字),并移动指针esp
由于ebp是栈底指针,esp是栈顶指针,正在调用哪个函数,ebp和esp就维护哪个函数的函数栈帧
后面调用Add函数的时候,ebp、esp就去维护Add函数的函数栈帧。
所以栈区中mian函数的调用空间(预开辟)就开辟好了连续push三次,将这三个元素依次放到栈顶,压栈三次
同时esp也移动到栈顶
lea:load effecitive address 加载有效地址
lea edi,[ebp-0E4h] 把 ebp-0E4h 的地址放到edi里面去,而ebp-0E4h应该就是main函数的栈顶地址
mov ecx,39h 把39h的值放入eax中去
mov eax,0CCCCCCCCh 把0CCCCCCCCh的值放入eax中
rep stos dword ptr es:[edi] 把从 edi(edp-0E4h)开始向下ecx(39h)次 dword 的数据全部都改成 eax(0CCCCCCCC) (dword(双字节))图例
简单的来讲就要将从 edp-0E4h 到 ebp 中间所有的内容初始化为0ECCCCCCCC
假设初始化完了(没有画全),意思就是中间全部初始化为0CCCCCCCC此时main函数才开始执行代码