函数栈帧(详解版)

article/2025/10/18 19:35:18

文章目录

  • 前言
  • 1.浅谈C语言内存
    • 1.1 内存分配
    • 1.2 栈
    • 1.3 寄存器
  • 2. 为main()函数开辟栈帧
  • 3.变量的初始化及函数调用
  • 4.ADD函数
    • 4.1 ADD函数的创建
    • 4.2 ADD函数使用与销毁
  • 5.总结


前言

前期学习的时候,我们可能有很多困感?
比如:
●局部变量是怎么创建的?I为什么局部变量的值是随机值?

●函数是怎么传参的?传参的顺序是怎样的?

●形参和实参是什么关系?

●函数调用过程是怎样的?

●函数调用结束后是怎么返回的?

学习函数栈帧的创建和销毁,其实就是修炼了自己的内功,也能搞懂后期更多的知识。我使用的环境是vs2019,不要使用太高级的编译器,越高级的编译器,越不容易学习和观察。同时在不同的编译器下,函数调用过程中栈帧的创建是略有差异的,具体细节取决于编译器的实现。


1.浅谈C语言内存

1.1 内存分配

在C语言中内存分别分为栈区(stack)、堆区(heap)、未初始化全局数据区、已初始化全局数据区、静态常量区(static)、代码区(data)。

1.2 栈

栈区(stack):存放函数参数和局部变量,以及函数调用开辟的栈帧;函数结束返回时自动释放空间。先进后出,向地址减小方向增长(由高地址到低地址),每一个函数调用都要在栈区开辟一块空间。

1.3 寄存器

寄存器往往直接被入在CPU逻辑电路中,速度量快,便于CPU快速调用。例如,我们今天会用到的eax,eba,ecx,edx以及比较重要的esp(栈顶指针),ebp(栈底指针)。这两个寄存器中存放的是地址,是用来维护函数栈帧的。

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

我们以以下这段代码为例:

int ADD(int x, int y)
{int z = 0;z = x + y;return z;
}
int main()
{int a = 20;int b = 10;int c = 0;c = ADD(a, b);printf("%d", c);return 0;
}

我们将上述代码的反汇编码调用出来,方便我们理解

int main()
{007518B0  push        ebp**//压入一个ebp(栈底)指针**007518B1  mov         ebp, esp**//使得ebp,esp指向同一位置**007518B3  sub         esp, 0E4h**//esp减0E4h(十六进制),也就是为main开辟了一块(esp-0E4h)这么大的空间

如图,为main函数栈帧的开辟过程。

		007518B9  push        ebx//压入一个ebx007518BA  push        esi//压入一个esi007518BB  push        edi//压入一个edi007518BC  lea         edi, [ebp - 24h]//将[ebp - 24h]这个地址放入edi中007518BF  mov         ecx, 9007518C4  mov         eax, 0CCCCCCCCh                               007518C9  rep stos    dword ptr es : [edi]//从edi往下,每次初始化四个字节,初始化ecx次这么多数据,全部初始化为 CCCCCCCC
/也就是说为main函数开辟的空间全部初始化为CCCCCCCC

如图,为main函数函数栈帧的初始化过程。
在这里插入图片描述

3.变量的初始化及函数调用

        int a=10;007518D5  mov         dword ptr[ebp - 8], 0Ah//将0Ah(也就是a的值)放在[ebp - 8]这个位置int b = 20;007518DC  mov         dword ptr[ebp - 14h], 14h//将14h(也就是b的值)放在[ebp - 14h]这个位置int c = 0;007518E3  mov         dword ptr[ebp - 20h], 0//将0(也就是c的值)放在[ebp - 20h]这个位置c = ADD(a, b);007518EA  mov         eax, dword ptr[ebp - 14h]//把b即([ebp - 14h])放到eax中007518ED  push        eax//在栈顶压入一个eax007518EE  mov         ecx, dword ptr[ebp - 8]//将a放在ecx中007518F1  push        ecx//在栈顶压入一个ecx007518F2  call        00751217//先将call指令的下一条语句的地址压入,然后调用add函数007518F7  add         esp, 8007518FA  mov         dword ptr[ebp - 20h], eax

相信不少小伙伴会疑惑为什么在执行call指令时,要先在栈顶压入call指令下一条语句的地址:,我们知道,我们的代码在编译器中是一条接着一条像下处理编译的,当我们调用结束ADD函数后,要继续执行ADD函数的下一条指令,这是我们提前保存到这个地址就起到了作用,方便我们快速的找到下一条指令,通俗讲就是从哪出去,从哪回来。
在这里插入图片描述

4.ADD函数

4.1 ADD函数的创建

int ADD(int x, int y)
{00F81770  push        ebp//压入一个ebp00F81771  mov         ebp, esp//使得压入的ebp指向和esp一样的位置00F81773  sub         esp, 0CCh//esp减0CC(十六进制)开辟了一块空间00F81779  push        ebx//压入一个ebx00F8177A  push        esi//压入一个esi00F8177B  push        edi//压入一个edi00F8177C  lea         edi, [ebp - 0Ch]//将[ebp - 0Ch]这个地址放入edi中00F8177F  mov         ecx, 300F81784  mov         eax, 0CCCCCCCCh00F81789  rep stos    dword ptr es : [edi]//将edi往下,每次初始化四个字节,初始化ecx次这么多数据,全部初始化为 0CCCCCCCCh

4.2 ADD函数使用与销毁

		int z = 0;00F81795  mov         dword ptr[ebp - 8], 0//将0放入[ebp - 8]中z = x + y;00F8179C  mov         eax, dword ptr[ebp + 8]//将[ebp + 8](即a)放在eax中00F8179F  add         eax, dword ptr[ebp + 0Ch]//将[ebp + 8](即a)与[ebp + 0Ch](即b)加起来00F817A2  mov         dword ptr[ebp - 8], eax//将eax放在[ebp - 8]的位置(即z的位置)return z;00F817A5  mov         eax, dword ptr[ebp - 8]//将[ebp - 8](即z)放在eax中,由于z是定义在ADD函数中的,因此出ADD函数z就会销毁,所以要将结果放在寄存器eax中
}
008917A8  pop         edi//将edi弹出
008917A9  pop         esi//将esi弹出
008917AA  pop         ebx//将ebx弹出
008917B8  mov         esp, ebp//使ebp指向esp的位置,即为将ADD开辟的函数栈帧销毁
008917BA  pop         ebp//将ebp弹出,使得ebp返回到main()函数ebp的位置
008917BB  ret//从栈顶弹出我们在main()函数中压入的call指令的下一指令的地址,使得我们找到call指令的下一条指令的位置

如图,为ebp指向esp的位置,即为将ADD开辟的栈帧销毁的示意图。
ebp指向esp的位置,为ADD开辟的栈帧销毁
如图,左下的ebp就是上述的" 008917BA pop ebp//将ebp弹出,使得ebp返回到main()函数ebp的位置 ",的示意图 。
在这里插入图片描述

 printf("%d", c);008918FD  mov         eax, dword ptr[ebp - 20h]//将eax(即z)放在[ebp -       20h](即c)中00891900  push        eax//将eax弹出00891901  push       897B30h/00891906  call        008910CD0089190B  add         esp, 8return 0;0089190E xor eax, eax
}

main()函数的销毁与ADD函数的销毁过程一样,我在这里就不在讲解。

5.总结

通过本文的讲解,我们最终了解了为什么创建的局部变量是随机值;函数是怎样传参的:在传值传参中,形参是实参的一份临时拷贝,传参的顺序是从右往左的,等等这样我们前面提到的问题。
最后,创作不易,希望大家能够点赞,关注,,如有错误,或不清楚的地方希望各位读者能够指出来,我会一 一回复。


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

相关文章

也谈栈和栈帧

 一个码农要是没遇见过coredump,那他就是神仙了。core file(coredump的转储文件)中保存的最重要内容之一,就是函数的call trace。还原这部分内容 (栈回溯) ,并与原代码对应上,尽快找出程序崩溃的位置和…

(栈帧和函数调用一)栈帧,函数调用与栈的关系

(栈帧和函数调用一)栈帧,函数调用与栈的关系 一,栈帧的介绍二,函数调用与栈的关系三,汇编演示四,总结 在计算机科学中,栈是一个特殊的容器,用户可以将数据压入栈中&#…

理解栈帧和栈的运行原理

栈中的数据都是以栈帧(Stack Frame)的格式存在,栈帧是一个内存区块,是一个数据集,是一个有关方法 (Method) 和运行期数据的数据集,当一个方法A被调用时就产生了一个栈帧 F1,并被压入到栈中&…

函数栈帧的形成与释放

✅作者简介:嵌入式入坑者,与大家一起加油,希望文章能够帮助各位!!!! 📃个人主页:rivencode的个人主页 🔥系列专栏:玩转C语言 💬推荐一…

【函数栈帧的创建和销毁】(超详细图解)

想必大家在学完C语言函数章节之后,是否有这样的困惑: 局部变量是怎么创建的 ? 为什么局部变量的值是随机值 ? 函数是怎么传参的?传参的顺序又是什么样的 ? 形参和实参是什么关系 ? 函数调用…

C语言函数栈帧详解

系列文章目录 前言 最近正在学习栈帧方面的知识,由于本人对汇编不太熟悉,对其中频繁出现的ESP寄存器和EBP寄存器一直没搞清楚,在查找资料后,在此进行整理,方便以后温故知新。 一、预备知识 要清楚理解栈帧的概念&…

详解栈帧结构

https://www.1024do.com/?p367 栈帧结构 含义:C语言中,每个栈帧对应着一个未运行完的函数。栈帧中保存了该函数的返回地址和局部变量。栈帧也叫过程活动记录,是编译器用来实现过程函数调用的一种数据结构。 从逻辑上讲,栈帧就是…

函数栈帧详解

目录 一.什么是函数栈帧 1.寄存器: 2.函数栈帧 3.栈帧的作用和维护 4.栈帧结构 二.函数栈帧的创建 1.汇编代码 2.main函数函数栈帧的创建 1.汇编语言讲解: 2.栈帧创建: 3.详细步骤 3.ADD函数栈帧的创建 栈帧创建: 3.函…

栈帧

首先应该明白,栈是从高地址向低地址延伸的。每个函数的每次调用,都有它自己独立的一个栈帧,这个栈帧中维持着所需要的各种信息。寄存器ebp指向当前的栈帧的底部(高地址),寄存器esp指向当前的栈帧的顶部(地址地)。下图为典型的存取器安排,观察栈在其中的位置 入栈操…

什么是函数栈帧

函数栈帧的创建与销毁 一、函数栈帧的创建1.寄存器2.函数栈帧3.函数中调用函数 二、函数栈帧的销毁总结 一、函数栈帧的创建 1.寄存器 一般来说,计算机中的寄存器有六种 分别是:eax, ebx, ecx,edx,ebp,esp 而ebp,esp这两个寄存器中存放的是地址&#…

栈帧 stack frame

栈帧 stack frame 每一次函数调用都会维护一个栈帧(stack frame),栈帧主要用于传递参数、保存返回地址、保存局部变量等。先直接上一个《深入理解计算机系统》上的原图。 其中,%rsp 指向栈顶位置,%rbp 指向栈底位置。…

C/函数栈帧

🌱博客主页:大寄一场. 🌱系列专栏:C语言学习笔记 😘博客制作不易欢迎各位👍点赞⭐收藏➕关注 ​ 目录 前言 寄存器 1. 寄存器的种类与功能 C语言汇编指令介绍 函数栈帧的创建与销毁过程 1.函数栈帧的…

浅谈函数栈帧(Stack Frame)

💙作者:阿润菜菜 📖专栏:C 本文目录 什么是栈帧 在调试中观察 总结 什么是栈帧 那我们先来看看什么是栈: 栈(stack)是限定仅在表尾进行插入或者删除的线性表。栈是一种数据结构,它按照后进先出的原则存储…

栈和栈帧

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

浅谈栈帧

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