depth: 3 title: 目录
函数栈帧(Function Stack Frame) 是程序运行时内存栈的组成部分,负责协调函数调用过程中的参数传递、局部变量存储、返回地址保存等关键操作。
栈(Stack)是计算机内存中一块特殊的区域,遵循 “先进后出(LIFO)” 的原则.
在程序运行时,操作系统会为进程分配一段连续的内存作为栈空间,用于支持函数调用。栈有两个关键的指针寄存器来管理:
栈的生长方向是从高地址向低地址,即每次向栈中压入数据(push),esp的值会减小;从栈中弹出数据(pop),esp的值会增大。
每一个函数被调用都需要在栈区创建一块地址
在VS2013中,调试程序打开调用堆栈,我们会发现--tmainCRTStartup()和mainStartup()两个函数也被调用了,接着是main()函数被调用

因此示意图如下

函数栈帧是指当一个函数被调用时,在栈上为其分配的一块独立内存区域。每个函数调用都会创建一个对应的栈帧,函数执行结束后,栈帧会被销毁(出栈)。
栈帧的作用:
一个典型的函数栈帧从高地址到低地址的结构如下:

call指令调用函数时,CPU会自动将下一条指令的地址(即函数执行完后要返回的位置)压入栈。push ebp),然后将当前esp的值赋给ebp(mov ebp, esp),以此建立自己的栈帧基地址。sub esp, N)在栈上开辟空间,用于存储局部变量。在VS2013编译器(基于x86架构)下,函数栈帧的实现细节与GCC略有差异,主要体现在栈帧布局、安全检查(如栈保护)和寄存器使用上。下面基于你的代码,结合VS2013的编译特性,重新解析函数栈帧的创建与销毁过程。
/GS编译选项(栈缓冲区安全检查),会在局部变量与返回地址之间插入"安全cookie"(随机值),防止缓冲区溢出攻击。__cdecl调用约定(默认),参数从右向左压栈,调用者负责清理参数。ebp(帧指针)和esp(栈顶指针)外,还会用到ecx、edx等寄存器辅助操作。push esi、push edi保存寄存器状态)。#include <stdio.h>
int add(int x, int y) {
int z = 0; // 语句 1
z = x + y; // 语句 2
return z; // 语句 3
}
int main() {
int a = 0; // 语句 4
int b = 0; // 语句 5
int c = 0; // 语句 6
c = add(a, b); // 语句 7
printf("%dn", c); // 语句 8
return 0; // 语句 9
}
VS2013中,程序启动后会先执行mainCRTStartup(C运行时初始化函数),最终通过call main进入main函数。
进入main函数后,首先执行栈帧初始化(VS2013会额外保存寄存器状态):
push ebp ; 保存调用者(CRT初始化函数)的ebp mov ebp,esp ; 建立main的栈帧基地址 push esi ; 保存esi寄存器(调试相关) push edi ; 保存edi寄存器(调试相关) sub esp,0E4h ; 开辟0E4h字节空间(包含局部变量、安全cookie和调试信息) lea edi,[ebp-0E4h] ; 加载栈帧底部地址到edi(用于初始化栈内存) mov ecx,39h ; 初始化39h个双字(4字节) mov eax,0CCCCCCCCh ; 填充值(VS调试模式下的未初始化内存标记) rep stos dword ptr es:[edi] ; 用0xCCCCCCCCh填充栈空间(方便调试时识别未初始化变量)
此时main函数栈帧的初始结构:

mov dword ptr [ebp-10h],0 ; 往[ebp-10h]写入0(a=0)
mov dword ptr [ebp-14h],0 ; 往[ebp-14h]写入0(b=0)
mov dword ptr [ebp-18h],0 ; 往[ebp-18h]写入0(c=0)
VS2013调试模式下,未初始化的局部变量会被填充为0xCCCCCCCCh(对应汇编指令int 3,用于触发调试中断),初始化后会覆盖该值。
mov eax,dword ptr [ebp-14h] ; 将b的值(0)从[ebp-14h]取到eax push eax ; 压入参数y(b=0),esp -= 4 mov ecx,dword ptr [ebp-10h] ; 将a的值(0)从[ebp-10h]取到ecx push ecx ; 压入参数x(a=0),esp -= 4
此时栈上新增参数:

call add ; 1. 压入返回地址(main中call的下一条指令);2. 跳转到add
call指令执行后,栈顶增加返回地址:

push ebp ; 保存main的ebp mov ebp,esp ; 建立add的栈帧基地址 push esi ; 保存esi寄存器 push edi ; 保存edi寄存器 sub esp,0CCh ; 开辟0CCh字节空间(局部变量、安全cookie等) lea edi,[ebp-0CCh] ; 初始化栈内存 mov ecx,33h ; 初始化33h个双字 mov eax,0CCCCCCCCh ; 填充未初始化内存标记 rep stos dword ptr es:[edi] ; 填充栈空间
此时add函数的栈帧结构(简化):

mov dword ptr [ebp-10h],0 ; 往[ebp-10h]写入0(z=0)
mov eax,dword ptr [ebp+8] ; 从[ebp+8]取x的值(0)到eax add eax,dword ptr [ebp+0Ch] ; 加上[ebp+0Ch]的y值(0),结果存eax(0) mov dword ptr [ebp-10h],eax ; 将结果写入z的地址[ebp-10h](z=0)
mov eax,dword ptr [ebp-10h] ; 将z的值(0)存入eax(返回值通过eax传递)
mov esp,ebp ; 释放add的栈空间(esp回到ebp位置) pop edi ; 恢复edi寄存器(main函数保存的状态) pop esi ; 恢复esi寄存器 pop ebp ; 恢复main的ebp(栈帧基地址) ret ; 弹出返回地址,跳转回main函数
ret执行后,add的栈帧完全销毁,esp指向参数x的位置。
add esp,8 ; esp += 8(清理栈上的两个参数x和y) mov dword ptr [ebp-18h],eax ; 将eax中的返回值(0)存入c的地址[ebp-18h](c=0)
add函数调用:先压入参数c(从[ebp-18h]取0),再压入格式字符串地址,然后call printf。printf内部会创建自己的栈帧,执行完后返回,main函数清理参数。xor eax,eax ; 将eax清零(返回值0) mov esp,ebp ; 释放main的栈空间 pop edi ; 恢复edi寄存器(CRT初始化时的状态) pop esi ; 恢复esi寄存器 pop ebp ; 恢复CRT的ebp ret ; 跳转回CRT函数,程序结束
以上为个人经验,希望能给大家一个参考,也希望大家多多支持本站。
您可能感兴趣的文章:AI短视频运镜教程:如何用AI制作莱坞级电影级镜头入门篇 心理映射镜头
开源的AI论文翻译/PDF格式转换工具:这两款AI神器完美翻译PDF论文
AI短视频运镜教程:如何用AI制作莱坞级电影级镜头入门篇 特殊效果镜头
AI短视频运镜教程:如何用AI制作莱坞级电影级镜头入门篇(电影空间的建构)
最近爆火的AI制作美女健身视频:AI生成俯卧撑女孩视频教程
怎样免费下载即梦AI无水印高清图片?关于即梦AI使用的小技巧