浅谈函数栈帧(Stack Frame)

article/2025/10/18 22:14:24

💙作者:阿润菜菜

📖专栏:C++


本文目录

什么是栈帧

 在调试中观察

总结


什么是栈帧

那我们先来看看什么是

栈(stack)是限定仅在表尾进行插入或者删除的线性表。栈是一种数据结构,它按照后进先出的原则存储数据。把数据元素存放到栈顶时,叫压栈(push) ,从栈顶删除一个元素,叫出栈(pop)。那什么是栈帧(Stack Frame)呢?

预备知识:

 每一次函数的调用,都会在调用(call stack)上维护一个独立的栈帧空间(stack frame).每个独立的栈帧一般包括:

  • 函数的返回地址和参数
  • 临时变量: 包括函数的非静态局部变量以及编译器自动生成的其他临时变量
  • esp、ebp这两个寄存器中存放的是地址,这两个地址是用来确认各变量,用来维护函数栈帧的
  • .ebp(栈底指针):该指针永远指向系统栈最上面一个栈帧的底部
  • esp(栈顶指针):该指针永远指向系统栈最上面一个栈帧的栈顶
  • 栈是从高地址向低地址延伸,一个函数的栈帧用ebp 和 esp 这两个寄存器来划定范围.ebp 指向当前的栈帧的底部,esp 始终指向栈帧的顶部
  • 压栈push :esp上移朝低地址移动;出栈pop:栈顶元素弹出,esp下移高地址

 在调试中观察

 我们使用的环境是VS2013,由于函数栈帧是底层知识,而越高级的编译器越难以抽离出函数栈帧分装的过程,不容易学习和观察。同时在不同的编译器下,函数调用栈帧的创建也是略有差异的,但大体思路都是一样的。

每一个函数独占自己的栈帧空间。当前正在运行的函数的栈帧总是在栈顶。 

如图: 

 在调用main函数的时候,会在栈中开辟一块空间,由ebp和esp共同来维护(在调用哪个函数,ebp和esp就会维护哪块空间)

但main函数是被怎么调用的呢?是被系统内提前建立好的函数栈帧调用的:

通过反汇编可以看到main函数是被_tmainCRTStartup()函数调用的,通过一系列汇编指令调用main函数同时esp和ebp来进行维护:  我们来看下这些汇编指令走的过程

 当执行压栈push时, ebp压到esp顶部,esp上移

执行move时,mov ebp,esp 就是把esp的地址交给edp

此时ebp和esp指向了同一个地址

 下一步是sub  esp,0E4h 就是把esp减去0E4h使esp上移。也就是为main函数开辟了空间

下面就是三个push:分别压进了ebx,esi和edi三个值(具体是什么值,无需关心,后面会自动弹出)

 接下来lea (load effective address)  就是为edi加载有效地址 [ebp - 0E4h]

通过下面mov和rep stos三个命令,我们把ebx到ebp之间的栈空间初始化为eax里的内容 

此时main函数的栈帧空间已开辟好,开始执行真正的有内容的代码:

32位中,word是两个字节,dword(double word)四字节

mov 把0Ah(也就是10)放到ebp-8的位置上,而ebp-8实际上就是为int a开辟一个空间 (局部变量int b =20 ,c = 0 的创建 与变量a 类似)

接下来就是调用Add函数了,我们可以看到是一条mov 指令,把[ebp -14h]值(也就是变量b值)放到eax中;然后就是push,压栈eax(b =20), 下面接着一条mov和push命令,类似压栈将变量a的值压入ecx;

 那么刚刚做的步骤是在为Add函数传参吗?是的。接下来call 命令就是调用,通过调试窗口我们可以清楚的看到a上面就是call指令的下一条指令的地址。这一步是在调用函数的同时把下一条指令的地址压上去,作为函数回归的标记

至此就来到我们的Add函数栈帧,与上面讲的main函数栈帧开辟一样。参数是从右向左压栈的,从上面我们也可以清楚的看到形参不是在Add函数内部创建的,而是回来到我们传参的空间,这也直接证明了形参是实参的临时拷贝这句话 !

 那Add函数是如何带回返回值的呢?我们知道函数调用完函数栈帧会销毁的,那函数栈帧都被销毁了,Add函数怎么传给返回值呢:可以看到把[ebp-8]里的值也就是int z 放到eax里面,因为这里的eax是寄存器(硬件)啊,寄存器不会因为程序退出就销毁的,相当于拿一个(全局的)寄存器把返回值保存起来,也就是拷贝了这个变量值。等到执行main函数我们再把它拿出来。

那么函数怎么返回呢?

 在 return z执行后,我们pop弹出,把栈顶的元素取出放到edi里面去,依次pop三次,esp指针就往下走。当我们函数调用完了那这个空间就没必要存在了,所以mov把ebp的地址给esp。

此时esp指到ebp,pop一下把栈顶的元素弹出来,因为里面放的是main函数的栈底指针,把结果弹到ebp里面去就可以瞬间到main栈底了

最后ret这条指令就是栈顶弹出call下一条指令地址然后跳过去,回来后就到了call下一条指令地方。此时add 就把形参的空间还给操作系统,然后把eax的值给[ebp-32]空间就是变量int c的空间。

觉得配合图示很难理解,大家可以结合实操快速掌握函数栈帧的创建和销毁过程

总结

在函数调用的过程中,有函数的调用者(caller)和被调用的函数(callee). 调用者需要知道被调用者函数返回值; 被调用者需要知道传入的参数和返回的地址

函数调用:

  • 参数入栈: 将参数按照调用约定(C语言是从右向左)依次压入系统栈中
  • 返回地址入栈: 将当前代码区调用指令的下一条指令地址压入栈中,供函数返回时继续执行
  • 代码跳转: 处理器将代码区跳转到被调用函数的入口处;
  • 栈帧调整:
    1.将调用者的ebp压栈处理,保存指向栈底的ebp的地址(方便函数返回之后的现场恢复),此时esp指向新的栈顶位置; push ebp
    2.将当前栈帧切换到新栈帧(将eps值装入ebp,更新栈帧底部), 这时ebp指向栈顶,而此时栈顶就是old ebp mov ebp, esp
    3.给新栈帧分配空间 sub esp, XXX

函数返回: 

  • 保存被调用函数的返回值到 eax 寄存器中 mov eax, xxx
  • 恢复 esp 同时回收局部变量空间 mov ebp, esp
  • 将上一个栈帧底部位置恢复到 ebp pop ebp
  • 弹出当前栈顶元素,从栈中取到返回地址,并跳转到该位置 ret

内容参考:系统栈的工作原理


 本文完。如有建议或问题欢迎评论区讨论


http://chatgpt.dhexx.cn/article/bUEyD9yU.shtml

相关文章

栈和栈帧

栈 堆栈(stack)又称为栈或堆叠,是计算机科学里最重要且最基础的数据结构之一,它按照FILO(First In Last Out,后进先出)的原则存储数据。 栈的相关概念: 栈顶和栈底:允许元素插入与删除的一端…

函数栈帧的创建和销毁(图解)

目录 基础知识介绍1. 寄存器的种类与功能2. 常用汇编指令3. 内存模型 演示函数栈帧的创建销毁过程1. 为main()函数开辟栈帧2. 在main()函数中创建变量3. 调用Add()函数前的准备4. 为Add()函数开辟栈帧5. 在Add()函数中创建变量并运算6. Add()栈帧的销毁7. 返回main()函数栈帧 总…

运行时栈帧结构是怎样的?

写在前面 本文隶属于专栏《100个问题搞定Java虚拟机》,该专栏为笔者原创,引用请注明来源,不足和错误之处请在评论区帮忙指出,谢谢! 本专栏目录结构和文献引用请见100个问题搞定Java虚拟机 解答 栈帧(Stack Frame)是J…

函数栈帧(详细图解)

目录 一、栈 二、常用寄存器及简单汇编指令 三、理解栈帧 3.1 main函数栈帧创建 3.1.1 main函数栈帧创建动态演示 3.2 局部变量创建 3.2.1 局部变量创建动态演示 3.3 函数传参与调用 3.3.1 函数传参 3.3.2 函数传参动态演示 3.3.3 函数调用 3.3.4 函数返回 四、END…

栈帧详解——C语言进阶

目录 传统艺能😎过渡区🤣正片开始👀寄存器👏main函数创建👏局部变量创建👏函数部分👏形参与实参👏 传统艺能😎 小编是大一菜鸟不赘述,欢迎大佬指点江山&…

栈帧结构详解

前言 Java虚拟机以方法作为基本的执行单位,“栈帧”是用于支持虚拟机进行方法调用和执行的数据结构,每一个方法从调用开始到执行结束,都对应着一个栈帧在虚拟机栈里面从入栈到出栈的过程,栈帧也是虚拟机运行时数据区中虚拟机栈的栈…

浅谈栈帧

一、 什么是栈帧? 什么是栈帧,首先引用百度百科的经典解释:“栈帧也叫过程活动记录,是编译器用来实现过程/函数调用的一种数据结构。 实际上,可以简单理解为:栈帧就是存储在用户栈上的(当然内…

栈帧(Stack Frame)

0x01.栈在计算机中的应用 在计算机系统中,栈也可以称之为栈内存是一个具有动态内存区域,存储函数内部(包括main函数)的局部变量和方法调用和函数参数值,是由系统自动分配的,一般速度较快;存储地址是连续且…

什么是栈帧

栈帧浅析 什么是栈帧 引用百度百科中的解释: 栈帧也叫过程活动记录,是编译器用来实现过程/函数调用的一种数据结构。函数的每次调用,都有它自己独立的栈帧。栈帧中维持着函数调用所需要的各种信息,包括函数的入参、函数的局部变…

【详解】函数栈帧——多图(c语言)

目录 前言 一.函数栈帧是什么? 二、栈帧准备知识 1.内存分区 2.什么是栈? 3.esp,ebp,eax寄存器 三、详解栈帧创建与销毁全过程 调用函数之前: 将传入函数的值放入栈中 函数执行: 1.保护当前ebp 2.…

【mcuclub】模数转换ADC0832

一、实物图 二、原理图 编号名称功能1CS片选使能,低电平芯片使能。2CH0模拟输入通道0,或作为IN/-使用。3CH1模拟输入通道1,或作为IN/-使用。4GND电源地5DI数据信号输入,选择通道控制。6DO数据信号输出,转换数据输出。7…

[技术讨论] [DDS] AD9833原理介绍及chiliDDS驱动分享(上)

​ 其实本文还有另一标题:《AD9833调不通?看这篇**就够了》 总觉字里行间隐隐霸气外露,不符合作者低调的风格,于是换了个朴素标题。标题狂不狂暂且不评,作者水平有限却是个事实;看到这篇**是你我缘分&…

ADC0832的使用

百度搜索ADC0832 Datasheet便可以免费获取该芯片的权威数据手册。 最重要的是查看它的时序及对应英文词组的意思。 CLK为时钟信号,需要外部输入,可直接与单片机引脚相连 Chip Select(CS):从Timing图中可以看出芯片工作期间要保持…

ADC0832的AD模数转换原理及编程

✅作者简介:嵌入式领域优质创作者,博客专家 ✨个人主页:咸鱼弟 🔥系列专栏:单片机设计专栏 目录 一、描述 二、模数转换原理: 三、模数转换的过程: 四、八位串行A/D转换器ADC0832简介&…

ADC0809的使用

一、前言介绍 使用ADC0809对一个模拟电压进行转换转换后的电压使用数码管显示出来 二、ADC0809的介绍 1、ADC0809简介 ADC0809是采用COMS工艺制造的双列直插式单片8位A/D转换器。分辨率8位,精度7位,带8个模拟量输入通道,有通道地址译码锁…

基于STM32F103RCT6的AD9833驱动开发(代码可以免费发邮箱)

基于STM32F103RCT6的AD9833驱动开发(代码可以免费发邮箱) AD9833手册分析 管脚定义: 手册就先讲到这里,不明白的欢迎评论区留言,另外我会把代码还有手册一并发送给感兴趣的朋友。 AD9833典型应用电路&#x…

AD9833数字信号发生器模块

简 介: 本文记录了使用快速制版测试AD9833这款数字信号发生器的内容。 关键词: AD9388,数字信号发生,快速制版 基于AD9833的正弦波,三角波,方波频率发生模块可以通过ZIGBEE来完成输入输出控制。其中还包括有…

STM32驱动AD9833模块

STM32驱动AD9833模块 前言软硬件准备一、本次使用的硬件二、代码 链接 前言 淘宝上买了个AD9833模块,stm32用商家的例程代码可以调频,可以调相,就是调不了幅度。换了几块不同32开发板都不行,重新以正点原子F103的工程为基础把驱动…

AD9833信号波形谐波

AD9833产生高频信号的谐波 ~ AD9833是一款AnalogDevices公司提供的数字信号可编程信号发生器芯片。它一般配有外置的主时钟信号,每次时钟信号将将内部28位的相位累加器递增一个相位数值。该相位数值由芯片SPI串口被外部的MCU设置。 相位累加器的高12位选择内部4096…

51驱动AD9833

使用51驱动AD9833模块的使用 关于AD9833相关参数程序流程代码片上传程序总结 原文链接:https://www.yourcee.com/newsinfo/2925703.html 关于AD9833 AD9833是一款低功耗、可编程波形发生器,能够产生正弦波、三角波和方波输出。各种类型的检测、信号激励…