文章目录一、栈的基础知识二、函数栈帧的定义与核心作用三、函数栈帧的结构解剖一个栈帧四、函数调用全过程栈帧的创建与销毁4.1VS2013的特殊栈帧特性4.2逐语句解析VS2013环境示例代码阶段1程序启动与main函数栈帧初始化阶段2main函数局部变量初始化语句4int a 0语句5int b 0语句6int c 0阶段3调用add函数参数传递与跳转语句7c add(a, b)步骤1传递参数从右向左压栈步骤2调用add函数阶段4add函数栈帧创建与执行进入add函数后栈帧初始化语句1int z 0语句2z x y语句3return z阶段5add函数栈帧销毁与返回阶段6main函数清理参数与处理返回值阶段7调用printf与程序结束语句8printf(%d\n, c)语句9return 0depth: 3 title: 目录函数栈帧Function Stack Frame是程序运行时内存栈的组成部分负责协调函数调用过程中的参数传递、局部变量存储、返回地址保存等关键操作。一、栈的基础知识栈Stack是计算机内存中一块特殊的区域遵循“先进后出LIFO”的原则.在程序运行时操作系统会为进程分配一段连续的内存作为栈空间用于支持函数调用。栈有两个关键的指针寄存器来管理espExtended Stack Pointer栈顶指针始终指向栈的最顶部最新压入的数据。ebpExtended Base Pointer栈基指针用于固定当前函数栈帧的起始位置方便访问栈帧内的元素。栈的生长方向是从高地址向低地址即每次向栈中压入数据pushesp的值会减小从栈中弹出数据popesp的值会增大。每一个函数被调用都需要在栈区创建一块地址在VS2013中调试程序打开调用堆栈我们会发现--tmainCRTStartup()和mainStartup()两个函数也被调用了接着是main()函数被调用因此示意图如下二、函数栈帧的定义与核心作用函数栈帧是指当一个函数被调用时在栈上为其分配的一块独立内存区域。每个函数调用都会创建一个对应的栈帧函数执行结束后栈帧会被销毁出栈。栈帧的作用存储函数的参数保存返回地址函数执行完后回到调用者的位置存储函数的局部变量保存调用者的栈基指针ebp以便函数返回时恢复调用者的栈帧提供临时数据的存储空间如表达式计算的中间结果。三、函数栈帧的结构解剖一个栈帧一个典型的函数栈帧从高地址到低地址的结构如下参数区函数参数由调用者压入栈位于被调函数栈帧的上方高地址侧。返回地址当使用call指令调用函数时CPU会自动将下一条指令的地址即函数执行完后要返回的位置压入栈。旧ebp被调函数执行时首先会将调用者的ebp压入栈push ebp然后将当前esp的值赋给ebpmov ebp, esp以此建立自己的栈帧基地址。局部变量区通过调整esp如sub esp, N在栈上开辟空间用于存储局部变量。内存对齐为了提高CPU访问效率栈帧大小通常会按4字节或8字节对齐因此可能存在填充空间。四、函数调用全过程栈帧的创建与销毁在VS2013编译器基于x86架构下函数栈帧的实现细节与GCC略有差异主要体现在栈帧布局、安全检查如栈保护和寄存器使用上。下面基于你的代码结合VS2013的编译特性重新解析函数栈帧的创建与销毁过程。4.1VS2013的特殊栈帧特性栈保护机制默认启用/GS编译选项栈缓冲区安全检查会在局部变量与返回地址之间插入安全cookie随机值防止缓冲区溢出攻击。参数传递仍遵循__cdecl调用约定默认参数从右向左压栈调用者负责清理参数。寄存器使用除ebp帧指针和esp栈顶指针外还会用到ecx、edx等寄存器辅助操作。调试信息会插入额外的调试相关指令如push esi、push edi保存寄存器状态。4.2逐语句解析VS2013环境示例代码#includestdio.hintadd(intx,inty){intz0;// 语句 1zxy;// 语句 2returnz;// 语句 3}intmain(){inta0;// 语句 4intb0;// 语句 5intc0;// 语句 6cadd(a,b);// 语句 7printf(%d\n,c);// 语句 8return0;// 语句 9}阶段1程序启动与main函数栈帧初始化VS2013中程序启动后会先执行mainCRTStartupC运行时初始化函数最终通过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函数栈帧的初始结构阶段2main函数局部变量初始化语句4int a 0mov dword ptr [ebp-10h],0 ; 往[ebp-10h]写入0a0语句5int b 0mov dword ptr [ebp-14h],0 ; 往[ebp-14h]写入0b0语句6int c 0mov dword ptr [ebp-18h],0 ; 往[ebp-18h]写入0c0VS2013调试模式下未初始化的局部变量会被填充为0xCCCCCCCCh对应汇编指令int 3用于触发调试中断初始化后会覆盖该值。阶段3调用add函数参数传递与跳转语句7c add(a, b)步骤1传递参数从右向左压栈mov eax,dword ptr [ebp-14h] ; 将b的值0从[ebp-14h]取到eax push eax ; 压入参数yb0esp - 4 mov ecx,dword ptr [ebp-10h] ; 将a的值0从[ebp-10h]取到ecx push ecx ; 压入参数xa0esp - 4此时栈上新增参数步骤2调用add函数call add ; 1. 压入返回地址main中call的下一条指令2. 跳转到addcall指令执行后栈顶增加返回地址阶段4add函数栈帧创建与执行进入add函数后栈帧初始化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函数的栈帧结构简化语句1int z 0mov dword ptr [ebp-10h],0 ; 往[ebp-10h]写入0z0语句2z x ymov eax,dword ptr [ebp8] ; 从[ebp8]取x的值0到eax add eax,dword ptr [ebp0Ch] ; 加上[ebp0Ch]的y值0结果存eax0 mov dword ptr [ebp-10h],eax ; 将结果写入z的地址[ebp-10h]z0语句3return zmov eax,dword ptr [ebp-10h] ; 将z的值0存入eax返回值通过eax传递阶段5add函数栈帧销毁与返回mov esp,ebp ; 释放add的栈空间esp回到ebp位置 pop edi ; 恢复edi寄存器main函数保存的状态 pop esi ; 恢复esi寄存器 pop ebp ; 恢复main的ebp栈帧基地址 ret ; 弹出返回地址跳转回main函数ret执行后add的栈帧完全销毁esp指向参数x的位置。阶段6main函数清理参数与处理返回值add esp,8 ; esp 8清理栈上的两个参数x和y mov dword ptr [ebp-18h],eax ; 将eax中的返回值0存入c的地址[ebp-18h]c0阶段7调用printf与程序结束语句8printf(%d\n, c)过程类似add函数调用先压入参数c从[ebp-18h]取0再压入格式字符串地址然后call printf。printf内部会创建自己的栈帧执行完后返回main函数清理参数。语句9return 0xor eax,eax ; 将eax清零返回值0 mov esp,ebp ; 释放main的栈空间 pop edi ; 恢复edi寄存器CRT初始化时的状态 pop esi ; 恢复esi寄存器 pop ebp ; 恢复CRT的ebp ret ; 跳转回CRT函数程序结束