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

article/2025/10/18 22:17:00

目录

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


电脑中的任何指令都是在CPU上的运行的,但是CPU本身只负责运算不负责存储,数据一般都是存储在内存和寄存器(储存最常用的数据)。
想要理解函数栈帧的创建和销毁,首先必须了解三个知识点:寄存器常用汇编指令内存模型

基础知识介绍

1. 寄存器的种类与功能

寄存器名称功能
eax累加寄存器,相对于其他寄存器,在运算方面比较常用。
ebx基地址寄存器,在内存寻址时存放基地址。
ecx计数寄存器,用于循环操作,比如重复的字符存储操作,或者数字统计。
edx作为EAX的溢出寄存器,总是被用来放整数除法产生的余数。
esi源变址寄存器,主要用于存放存储单元在段内的偏移量。通常在内存操作指令中作为“源地址指针”使用。
edi目的变址寄存器,主要用于存放存储单元在段内的偏移量。
eip控制寄存器,存储CPU下次所执行的指令地址(存放指令偏移地址)。
esp栈顶指针,堆栈的顶部是地址小的区域,压入堆栈的数据越多,esp也就越来越小。在32位平台上,esp每次减少4字节。栈指针寄存器(extended stack pointer),其内存放着一个指针,该指针永远指向系统栈最上面一个栈帧的栈顶。是CPU机制决定的,pushpop指令会自动调整esp的值。
ebp基址指针,指栈的栈底指针。基址指针寄存器(extended base pointer),一般与esp配合使用,可以存取某时刻的esp,这个时刻就是进入一个函数内后,CPU会将esp的值赋给ebp,此时就可以通过ebp对栈进行操作,比如获取函数参数,局部变量等。其内存放着一个指针,该指针永远指向系统栈最上面一个栈帧的底部。

2. 常用汇编指令

  • push指令:它首先减少esp的值,再将源操作数复制到栈地址,在32位平台上,esp每次减少4字节。
    image-20210810154859463
    解释:首先esp的值减少4字节,再将ebp的值压入栈中。
  • pop指令:它首先把esp指向的栈元素内容复制到一个操作数中,再增加esp的值。在32位平台上,esp每次增加4字节。
    image-20210810183117747
    解释:首先将esp所指地址处的值赋给edi,再将esp的值减少4字节。
  • mov指令:用于将一个数据从源地址传送到目标地址,源操作地址的内容不变。

    解释:将esp 值赋给ebp,这里并不是将esp所指向的内存空间的值赋给 ebp
  • sub指令:减操作指令,从寄存器中减去<shifter_operand>表示的数值,并将结果保存到目标寄存器中。

    解释:esp-0E4h字节的结果保存在esp中。

  • lea指令:是“load effective address”的缩写,简单的说,lea指令可以用来将一个内存地址直接赋给目的操作数。

    解释:将ebp-0E4h的值直接赋给edi,而不是把ebp-0E4h内存地址里的数据赋给eax。

  • rep指令:重复前缀指令,英文缩写 repeat。能够引发其后字符串指令被重复。

  • stos指令:串存储指令,英文缩写 store string。
    image-20210810164731501
    解释
    上述几条指令通常一起使用,
    rep指令重复其上面的指令,ecx的值是重复的次数,每执行一次,ecx 减 1,直到 ecx 减至0。
    stos指令eax中的值拷贝到es:[edi]指向的地址。
    dword双字 就是四个字节。
    ptrpointer缩写 即指针
    [ ]里的数据是一个地址值,这个地址指向一个双字型数据
    一次拷贝双字(4个字节)的数据到目的地址。
    es:[edi]指向目的串
    解释:合起来的意思就是,将栈上从 ebp-0E4h开始的位置,向高地址方向的内存赋值 0CCCCCCCCh,重复 39h 次,每次赋值双字(四字节的空间)。

  • call指令:将程序下一条指令的位置的IP压入堆栈中,并转移到调用的子程序。
    image-20210810171254154
    解释:将下一条指令的IP(00BF1A30)压入栈中,并移动到调用的子程序。

  • jmp指令:无条件跳转指令。
    image-20210810171429337
    解释:无条件跳转到IP为(0BF3BE0H)的位置。

  • add指令:用于将两个运算子相加,并将结果写入第一个运算子。
    image-20210810185459552
    解释:给 esp 加8,也就是 esp向高地址方向移动 8字节 ,相当于 pop操作后的指针变化。

  • ret指令:用于终止当前函数的执行,将运行权交还给上层函数。也就是,当前函数的帧将被回收。
    image-20210810171719031
    解释:执行这条命令之后,就自动返回刚才call指令的下一行。
    image-20210810171755047

3. 内存模型

image-20210812113541613

从逻辑上讲,栈帧就是一个函数执行的环境:函数参数、函数的局部变量、函数执行完后返回到哪里等等。
首先应该明白,栈是从高地址向低地址延伸的。每个函数的每次调用,都有它自己独立的一个栈帧,这个栈帧中维持着所需要的各种信息。寄存器ebp指向当前的栈帧的底部(高地址),寄存器esp指向当前的栈帧的顶部(低地址)。

这次演示所使用的环境是windows 10、编译环境 vs2013(debug、Win32)。

在不同的编译器下,函数调用过程中栈帧的创建是略有差异的,具体细节取决于编译器的实现。

友情提示:

不要使用太高级的编译器,越高级的编译器,越不容易学习和观察。

演示函数栈帧的创建销毁过程

首先来看下这次演示使用的代码:

// 为了能够观察全部的细节,所以把代码拆的足够细。
#include <stdio.h>int Add(int x, int y)
{int z = 0;z = x + y;return z;
}
int main()
{int a = 10;int b = 20;int c = 0;c = Add(a, b);printf("%d\n", c);return 0;
}

按下F10,在视图中打开调用堆栈窗口,我们发现main()函数被调用了。

但是main()函数被谁调用了呢?

image-20210810154724877

当我们接着调试到return 0;之后,再按F10,我们发现程序跳转到了调用main()函数的函数内

image-20210810165654723

原来main()函数是被__tmainCRTStartup函数调用的,而 __tmainCRTStartup又是被mainCRTStartup调用的。

接下来分步骤演示函数栈帧的创建和销毁的过程。

1. 为main()函数开辟栈帧

image-20210811155928381
m1

2. 在main()函数中创建变量

image-20210811070952006

main3

3. 调用Add()函数前的准备

image-20210811074733127

main5

4. 为Add()函数开辟栈帧

image-20210810181337849在这里插入图片描述

5. 在Add()函数中创建变量并运算

image-20210811200819392

Add2

6. Add()栈帧的销毁

image-20210811080322450

m3

7. 返回main()函数栈帧

image-20210811081128187

可以看到这里返回到了第3步(3. 调用Add()函数前的准备),最后指令call的下一条指令。

之后的过程还很复杂,这里就不详细展示了。

有兴趣的铁铁们可以自己研究研究。

总结

看到这里,想必以前学习中的许多困惑已经有了答案吧。

比如:

  1. 局部变量是怎么创建的?
  2. 为什么局部变量的值是随机值?
  3. 函数是怎么传参的?传参的顺序是怎样的?
  4. 形参和实参是什么关系?
  5. 函数调用是怎么做的?
  6. 函数调用是结束后怎么返回的?

希望可以对大家有所帮助,如果有什么不对的地方,还烦请指教,谢谢!


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

相关文章

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

写在前面 本文隶属于专栏《100个问题搞定Java虚拟机》&#xff0c;该专栏为笔者原创&#xff0c;引用请注明来源&#xff0c;不足和错误之处请在评论区帮忙指出&#xff0c;谢谢&#xff01; 本专栏目录结构和文献引用请见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语言进阶

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

栈帧结构详解

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

浅谈栈帧

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

栈帧(Stack Frame)

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

什么是栈帧

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

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

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

【mcuclub】模数转换ADC0832

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

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

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

ADC0832的使用

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

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

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

ADC0809的使用

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

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

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

AD9833数字信号发生器模块

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

STM32驱动AD9833模块

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

AD9833信号波形谐波

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

51驱动AD9833

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

Arduino + AD9833 波形发生器

Arduino SI5351 方波发生器_姜戈12的博客-CSDN博客SI5351 方波发生器https://blog.csdn.net/jiangge12/article/details/125815044 感觉 Si5351 只有方波还是少点意思。 看到有人做 AD9833 &#xff0c;成品卖355元。https://www.bilibili.com/video/av463721457/ 上面视频…

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

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