函数栈帧的创建与销毁
寄存器
寄存器(Register)是 CPU 内部极高速的存储单元,用于暂存指令、数据和地
址。它的主要作用是减少 CPU 访问内存的次数,从而极大提升程序执行速度。
常见的寄存器有 eax、ebx、ecx、edx、ebp、esp 等
其中 ebp(栈顶指针) 、 esp(栈底指针) 是用于维护函数栈帧的
main 函数也是被调用的
本文的所有代码都是在 visual studio 2013 中运行
1 | int Add(int x, int y) |
这段代码在进入调试后,可以看到如图所示


main() 函数也是被调用的,main()在 __tmainCRTStartup()函数中被调用,而
__tmainCRTStartup() 在 mainCRTStartup() 中被调用
栈区
C语言中通常会将内存划分为 静态区(也称全局/静态存储区)、堆区 和 栈区 三
个核心区域。
静态区:存放全局变量、静态变量(static 修饰的局部或全局变量)以及字
符串常量。这部分内存在程序编译时就已确定大小,并在程序整个运行期间都存
在(从加载到退出)。
堆区:用于动态内存分配(如 malloc/new)。由程序员手动申请和释放(若不释
放,程序结束时由操作系统回收)。堆区的大小受限于系统虚拟内存,相
对较大,但分配速度较慢,容易产生内存碎片。
栈区:存放局部变量、函数参数、函数调用返回地址等。由编译器自动管
理,遵循“后进先出”(LIFO)规则。栈区大小较小(通常几 MB),分配速
度极快,但超出容量会导致栈溢出。

栈区的使用习惯是从高地址向低地址使用,但具体的要视编译器而定
在 visual studio 2013 调试环境中,栈区的使用是从高地址到低地址

汇编查看函数栈帧的创建与销毁过程
1 | int Add(int x, int y) |
这段代码在进入调试后,转到反汇编

上文说到在调用 main() 函数前会调用__tmainCRTStartup(),也就是
此时占栈中如图所示

先看创建变量 a 前的汇编代码,取消勾选显示符号名


红框中框选的汇编代码 push 表示压栈 , 将 ebp 压入栈中, 压栈结束
后,esp 会自动指向压入的变量
再往后走一步

这里红框选中的汇编代码, mov 表示赋值, 将 ebp 的值赋值给 esp
此时查看 ebp esp 中的值

再往后走一步

这里红框选中的汇编代码, sub 表示减等, 将 esp -= 0E4H 0E4H是一个
十六进制数,换算成十进制数是 228
此时查看 esp 中的值

再往后走一步

这里红框选中的汇编代码, push 表示压栈, 将栈中压入
ebx esi edi , 压
栈结束后,esp 会自动指向压入的变量
查看 ebx esi edi
再往后走一步

这里红框选中的汇编代码, lea 表示load effctive address, 加载有效地址,
表示在 ebi 中加载的是 ebp - 0E4h 这块大小的地址
调试查看 ebi 的值

而这一段汇编语言是在将从 edi 指向的位置开始,向下 ecx 个
dword(double word 一个 word 表示两个字节) 的数据全部赋值为
0xCCCCCCCCh

再往后走一步

这里红框选中的汇编代码, 表示向 ebp - 8 指向的内存空间中放入
十六进制数 0Ah,也就是 10 , 就是变量 a

图中汇编代码表示向 ebp - 14h(20) 和 ebp - 20h(32) 指向的内存空间
处放入 14h(20,变量 b) 和 0(变量c)

图中红框选中的汇编代码是将 ebp - 14h 指向内存空间中的值(即变量
b)赋值给 eax , 将 ebp - 8 指向内存空间中的值(即变量a)赋值给 ecx ,并
将 eax ecx 压入栈中

图中红框选中的汇编代码 call 是调用函数,同时 call 指令下一条指令的
地址 00091AA0 压入栈中

这是进入 Add 函数后的汇编代码,红框选中的就是在为 Add 函数
创建空间,并压对应的寄存器入栈,为变量 sum 分配空间

图中红框选中的汇编代码是将 ebp + 8 的值赋值给 Add 中的 eax , eax
再加等 ebp + 0Ch(十进制数12),这实际上就是再传参,显然这里是从
Add 函数栈帧外拿值放到 Add 函数内的 eax 中,这也就能解释为什么形
参是实参的一份临时拷贝,加等的值赋值给 ebp - 8(即变量 sum )

图中红框选中的汇编代码是将 ebp - 8 的值(即变量 sum )的值保存在
Add 函数中的 eax 中, 然后依次 pop 也就是弹出 edi esi ebx , 弹出与压
栈是相反的,每弹出一个元素 esp 会自行指向弹出后的栈底空间,弹出
后再将主函数中的 ebp 赋给 esp , 然后再弹出 ebp, 此时可以发现,esp
指向了前文中 call 指令下一句指令,ret 表示返回值

图中红框选中的汇编代码是将 esp + 8 , 即释放了形参 x 和 y 的空间,同
时将函数的返回值赋值给了 ebp - 20h 即变量 C
到这里主函数 main 和 Add 函数的栈帧的创建和销毁就分析完成了





