函数栈帧的创建与销毁

这篇具有很好参考价值的文章主要介绍了函数栈帧的创建与销毁。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

函数栈帧的创建与销毁,魔王的修炼之路——C语言,c语言,c++,算法

  • 魔王的介绍:😶‍🌫️一名双非本科大一小白。
  • 魔王的目标:🤯努力赶上周围卷王的脚步。
  • 魔王的主页:🔥🔥🔥大魔王.🔥🔥🔥
    函数栈帧的创建与销毁,魔王的修炼之路——C语言,c语言,c++,算法
    ❤️‍🔥大魔王与你分享:莫泊桑说过,生活可能不像你想象的那么好,但是也不会像你想象的那么糟。人的脆弱和坚强都超乎了自己的想象。有时候可能脆弱的一句话就泪流满面,有时候你发现自己咬着牙已经走过了很长的路。

一、前言

我们在编译代码时会有很多不清楚的地方,例如我们创建变量时我们只知道会开辟空间,却不知道要在哪开辟,怎么开辟,在我们调用函数时,我们也知道要在栈区开辟空间,但是依然不知道怎么开辟,参数如何拷贝,为什么值传递不会改变原数值。本篇博客带你理解栈区(局部变量和函数调用开辟空间的地方)是怎样工作的。

二、问题举例

  • 局部变量是怎么创建的?
  • 为什么局部变量的值是随机值?
  • 函数时怎么传参的?传参的顺序是怎样的?
  • 形参和实参是什么关系?
  • 函数调用时怎么做的?
  • 函数调用时结束后怎样返回的?
  • 通过下面的讲解你将全部明白这些问题

三、介绍

  • 首先说明一些比较陌生的东西,以便后续的理解。

1.寄存器

  1. 寄存器:中央处理器内的组成部分。寄存器是有限存贮量的高速存贮部件,它们可以来暂存指令、数据和地址。寄存器是独立于内存的,不会因为栈区函数空间的销毁而销毁。
  2. 寄存器举例:
    eax、ebx、ecx、edx、ebp、esp等
    其中三个寄存器比较重要:
    1.eax:接收函数返回值,使得函数的返回值不会因为栈区的销毁而消失。
    2.ebp:栈底寄存器(高地址),和esp共同维护栈区新开辟的空间。
    3.esp:栈顶寄存器(低地址),和ebp共同维护栈区新开辟的空间。

2.汇编指令

push:压栈,放入栈顶一个元素(寄存器的内容,并不是寄存器)。
pop:出栈,把栈顶数据弹出,并把弹出的元素赋值到寄存器中。
move:给一个寄存器赋值。
sub:减法指令。
add:加法指令。
lea:即load effective address,加载有效地址。
call:调用函数,并且把该函数之后的地址进行压栈(目的是为了在函数销毁后可以回到函数之后的下一步)。
dword:即double word,可以理解为两倍的字的大小,我们知道一个汉字两个字节,那么两倍的就是四个字节。
rep stors:重复拷贝寄存器中的数据。
注意:esp永远指向栈顶,每次压栈后esp就会自动减去4个字节(因为栈区的使用是高地址向低地址使用),每次出栈后esp就会加上4个字节。

四、讲解

  • 以此代码为标准进行详细说明:
#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;
}

1.main函数并不是最终函数

我们一直认为main函数结束,程序就会结束,main函数是程序的最后一个函数,你是否想过这样一个问题:为什么main函数会有返回值呢?那自然是因为main函数并不是最终函数,main函数也是被其他函数所调用的。我们转到调用堆栈就会观察到,如图:

函数栈帧的创建与销毁,魔王的修炼之路——C语言,c语言,c++,算法

  • 那么到底是什么函数调用的main函数呢?
    函数栈帧的创建与销毁,魔王的修炼之路——C语言,c语言,c++,算法

是__tmainCRTStartup函数调用的main函数。__tmainCRTStartup函数又是被mainCRTStartup函数调用的。

  • 也就是main函数后其实还有两个函数,如图:函数栈帧的创建与销毁,魔王的修炼之路——C语言,c语言,c++,算法

所画的图只表示调用顺序和栈帧使用顺序(先使用高地址在使用低地址)。

2.函数栈帧的创建

函数栈帧怎样创建,我们可以通过反汇编观察得到。要进入反汇编,首先要进入调试,然后按照图片操作即可进入,如图:

函数栈帧的创建与销毁,魔王的修炼之路——C语言,c语言,c++,算法

  • main函数的栈帧创建

函数栈帧创建的逻辑基本一样,所以详细说一个怎么创建,其他就基本也是这样,这里以主函数举例。通过图一点点进行分析,如图:函数栈帧的创建与销毁,魔王的修炼之路——C语言,c语言,c++,算法

所画的15个指令是在Add函数之前的汇编指令,前10个指令是创建主函数的栈帧空间并赋值为CCCCCCCC,这就是当我们不给一个变量赋值时,会打印出烫烫烫烫的原因。第11、12这两个指令是新增的,在VS2013上并没有,所以无视就行。那么从第13个指令开始,就该使用所开辟好的空间了。详细如下:

  1. 用ebp的值进行压栈。然后esp-4(没个元素都是4个字节,栈区的使用是从高地址向低地址使用的)。
  2. 把esp的值赋给ebp。
  3. 让esp减去0E4h这个十六进制数字。(目的是给main函数开辟空间,esp是维护栈顶的,所以要让esp移动到新开辟的栈顶位置)
  4. 用ebx的值进行压栈。
  5. 用esi的值进行压栈。
  6. 用edi的值进行压栈。
  7. load effective address,加载有效地址。放入edi中。
  8. 把9赋值到ecx中。
  9. 把CCCCCCCC赋值到eax中。
  10. 重复拷贝:从edi(加载的那个有效地址)开始,拷贝ecx次(9),每次拷贝eax(CCCCCCCC),每次拷贝的大小为dword(4字节)。每拷贝一次,edi(存放有效地址的这个寄存器都加4),ecx(存放拷贝次数的寄存器都减1),直到ecx减到0,停止拷贝,执行下一个汇编指令。
    前十个是为main函数开辟空间并赋值为CCCCCCCC的汇编码。接下来才开始让我们写的程序分配空间。
  11. 在ebp-8位置处赋值0Ah(也就是10).
  12. 在ebp-14h(ebp-20)位置处赋值14h(也就是20)
  13. 在ebp-20h(ebp-32)位置处赋值0
  • 如图:
    函数栈帧的创建与销毁,魔王的修炼之路——C语言,c语言,c++,算法

第一个图其实__tmainCRTStartup函数是被mainCRTStartup函数调用的,不过这里就不画了,我们主要是理解main函数及main函数内的函数调用如何在栈区开辟空间就好了,main函数之前的那两个函数知道有就行了。

这个图如果在VS2013是正确的,开辟的空间全部先初始化,但是在VS2022中,其实开辟的主函数的空间并没有全部赋值,如图:

函数栈帧的创建与销毁,魔王的修炼之路——C语言,c语言,c++,算法

  • 最后一次拷贝后的内存展示:函数栈帧的创建与销毁,魔王的修炼之路——C语言,c语言,c++,算法

3.函数调用

开辟空间前执行的操作:

  • 函数调用的时候,我们都知道会开辟空间,但是他是怎么开辟的呢?
  • 我们知道形参是实参的临时拷贝,那么它是怎样拷贝的呢?
  • 我们知道函数结束后该函数所处的栈帧空间就要被销毁回收,那么如果该函数有返回值,明明已经被销毁了,它是怎样再返回去的呢?
    接着往下看,你就会全部知道。
  • 在调用函数这个操作中,刚开始进行的是让实参(实参地址中的内容那就是实参的数值)赋给eax,ecx,从右向左压栈,也就是先压栈b的值,再压栈a的值,这就是反汇编的前四行。其实这四行目的就是拷贝实参。第五行call指令是调用Add这个函数并且把调用Add函数后的下一个操作指令进行压栈,目的是为了在调用函数结束后,可以回到Add函数之后的下一步指令。
    函数栈帧的创建与销毁,魔王的修炼之路——C语言,c语言,c++,算法

开辟空间及销毁前的操作:

  • 汇编指令如图:函数栈帧的创建与销毁,魔王的修炼之路——C语言,c语言,c++,算法
    函数栈帧的创建与销毁,魔王的修炼之路——C语言,c语言,c++,算法

红色框不含黄色部分:
还是和之前main函数开辟一样,先是压栈ebp,然后让ebp移动到esp位置,然后esp减去一个数值(即开辟的空间大小),然后进行(部分)(不同编译器可能不一样,有可能使部分赋值,有可能是整个空间赋值)赋值,赋值为CCCCCCCC,然后就是为Add函数中的变量分配空间(如下图)。
红色框中的黄色部分:
这个部分是关于函数返回值的汇编指令,两个图结合看,ebp+8的值赋给eax(是寄存器,独立于内存,不会因为栈帧空间销毁而销毁),然后让eax的值加上ebp+0Ch这个地址的值,这个地址转换为十进制也就是ebp+12,再让eax的值赋给该函数中的变量z,所以函数的返回值也就暂时储存在了寄存器eax中(因此当z被销毁后,eax里还存着函数的返回值,等待之后的指令),那么你是否发现了,ebp+8其实就是在开辟函数空间前拷贝的10,ebp+12就是开辟空间前拷贝的20,所以eax就变成了30,也就是说函数使用的实参其实在自己内部空间根本就没有,他们只是通过地址访问了在开辟函数空间前压栈的那些形参,仅仅是使用而已,因此值传递不改变原数值。

销毁操作:

函数栈帧的创建与销毁,魔王的修炼之路——C语言,c语言,c++,算法

  • 按照老编译器(老编译器中没有划掉的那三行)来说:
  • 第1步:把z的值赋给eax,因为z要销毁了,但是返回值要被保留,所以要借助寄存器保留下来。
  • 2~4步:pop指令,也就是出栈,最上面的三个元素,并把元素的值分别赋给edi、esi、ebx,其实这三个pop指令中的赋值操作没用,因为弹出的是edi,edi里边本来存的就是edi的值。其他两个也是这样。但是对于倒数第二行的pop指令中的赋值操作就用处很大了,接着看,等会会说到(第6步)。
  • 第5步:把ebp的值赋给esp,那就是说esp离开了正在维护的函数的栈帧空间,那么这一部分就让函数栈帧空间被销毁了。
  • 第6步:出栈,并且把出栈的值(是ebp指向main函数底部时压栈上去的那个地址,也就是说出栈的这个元素其实存放的是main函数的栈底指针)赋给寄存器ebp。那么ebp就回到原来的位置了。(main函数的栈底指针处)
  • 第七步:ret,即返回,这一步也很重要,它的意义是弹出并回到弹出的这个地址所指向的指令,因为第六步我们只是让ebp回到了main的栈底位置,此时esp和ebp位置都就绪了,但是指令却还没有返回,而这一步其实就是弹出了调用函数之后的那个指令的地址(之前压栈上去的那个),并且回到弹出这个指令的位置,也就是调用函数的下一个指令的位置,那么一切便结束了,后面就是调用函数之后的操作了。

4.调用函数后的汇编指令:

  • 最后两行指令:
    函数栈帧的创建与销毁,魔王的修炼之路——C语言,c语言,c++,算法
  • 目前的栈帧图:
    函数栈帧的创建与销毁,魔王的修炼之路——C语言,c语言,c++,算法

第一个指令esp加8,那么就是说esp跳过了临时拷贝的变量,也就是销毁了临时拷贝形参的栈帧空间。
第二个指令就是把eax寄存器中存的函数的返回值赋到main函数中接收Add函数返回值的地址中,也就是变量c的位置,往上翻看看前面你就会发现变量c的地址就是eax要赋的地址处。

  • 那么相信你看完函数栈帧的创建于销毁后对栈帧空间的运行原理有了更深层次的理解。

五、总结

函数栈帧的创建与销毁,魔王的修炼之路——C语言,c语言,c++,算法

✨请点击下面进入主页关注大魔王
如果感觉对你有用的话,就点我进入主页关注我吧!文章来源地址https://www.toymoban.com/news/detail-618163.html

到了这里,关于函数栈帧的创建与销毁的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • C语言之反汇编查看函数栈帧的创建与销毁

    函数栈帧是用于在计算机程序中实现函数调用的一种数据结构。在函数调用过程中,每个函数都需要在内存中创建一个栈帧,用于存储局部变量、返回地址和参数等。 具体来说,函数栈帧通常包含以下部分: 局部变量表:存储函数的局部变量,包括基本数据类型(如整数、

    2024年01月23日
    浏览(37)
  • 【C语言__函数栈帧的创建和销毁__复习篇9】

    目录 前言 一、知识补充 二、分析创建和销毁的过程 三、前言问题回答 本篇主要讨论以下问题: 1. 编译器什么时候为局部变量分配的空间 2. 为什么局部变量的值是随机的 3. 函数是怎么传参的,传参的顺序是怎样的 4. 形参和实参是什么关系 5. 函数调用是怎么做的 6. 函数调

    2024年04月25日
    浏览(38)
  • 打通你学习C语言的任督二脉-函数栈帧的创建和销毁(上)

      🌈个人主页:  Aileen_0v0 🔥系列专栏: C语言学习 💫个人格言: \\\"没有罗马,那就自己创造罗马~\\\" 待解决疑惑: 局部变量是怎么创建的? 为什么局部变量的值是随机值? 函数是怎么传参的?传参的顺序是怎样的? 形参和实参是什么关系? 函数调用是怎么做的? 函数调用是结束后怎么返

    2024年02月05日
    浏览(35)
  • 函数栈帧的创建和销毁

    前言 观察函数栈帧的创建和销毁,不要使用太高级别的的编译器,越高级的编译器越不容易学习和观察。同时在不同编译器下,函数调用的过程中栈帧的创建是略有差异的,具体细节取决于编译器的实现 我们在写C语言代码的时候,经常会把一个独立的功能抽象为函数,所以

    2023年04月17日
    浏览(44)
  • 函数栈帧的创建与销毁

    魔王的介绍:😶‍🌫️一名双非本科大一小白。 魔王的目标:🤯努力赶上周围卷王的脚步。 魔王的主页:🔥🔥🔥大魔王.🔥🔥🔥 ❤️‍🔥大魔王与你分享:莫泊桑说过,生活可能不像你想象的那么好,但是也不会像你想象的那么糟。人的脆弱和坚强都超乎了自己的想

    2024年02月15日
    浏览(37)
  • 从汇编代码探究函数栈帧的创建和销毁的底层原理

    人,只有在放弃战斗的时候才算输,只要坚持战斗,就还没输  本文收录于青花雾气-计算机基础 往期回顾 从0到1搞定在线OJ 数据在内存中的存储 计算机存储的大小端模式 目录 一、先导知识 二、函数调用堆栈 三、函数栈帧的创建 1.创建函数栈帧 2.创建变量 3.函数传参 4.函数

    2024年02月08日
    浏览(40)
  • 【C语言】函数栈帧的创建和毁销

    大家好,我是深鱼~ 目录 一、寄存器 二、栈区  三、函数栈帧的创建 1.为main函数开辟栈帧  2.在main函数中创建变量 3.调用Add函数前的准备  4.为Add函数开辟栈帧  5.在Add函数中创建变量并运算 四、函数栈帧的销毁 6.Add函数栈帧的销毁 7.返回main函数栈帧 【前言】 前期学习的时

    2024年02月14日
    浏览(32)
  • 函数栈帧的创建和毁销【C语言版】

    大家好,我是深鱼~ 目录 一、寄存器 二、栈区  三、函数栈帧的创建 1.为main函数开辟栈帧  2.在main函数中创建变量 3.调用Add函数前的准备  4.为Add函数开辟栈帧  5.在Add函数中创建变量并运算 四、函数栈帧的销毁 6.Add函数栈帧的销毁 7.返回main函数栈帧 【前言】 前期学习的时

    2024年02月15日
    浏览(40)
  • 探秘函数栈帧:『 揭开函数栈帧创建与销毁的神秘面纱 』

    .. 目录 知识点回顾 一、什么是栈帧(堆栈帧)? 1.内存布局 2.常用寄存器 3.汇编指令 👇👇对于栈的详细介绍 : 👇👇函数栈帧的介绍: 二、函数调用中的栈帧 1.探究main函数栈帧的创建 2.对main函数中的代码进行分析 3.探究Add函数栈帧的创建  三、函数栈帧的销毁过程 博客引

    2024年02月06日
    浏览(77)
  • 学C的第十一天【查看汇编代码一步步了解 函数栈帧(栈区局部变量)的创建和销毁】

    ========================================================================= 相关代码gitee自取: C语言学习日记: 加油努力 (gitee.com) ========================================================================= 接上期: 学C的第十天(继续深入学习函数、函数递归、练习)-CSDN博客 ============================================

    2024年02月04日
    浏览(39)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包