c语言(函数栈帧的创建和销毁)

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

前沿:

        可能很多人也是第一次听说函数栈帧这个词,想问什么是函数栈帧,理解函数栈帧有什么作用,函数栈帧的创建销毁是什么呢?这章节我们就来了解一下c语言中函数栈帧的创建和销毁。

思维导图:

c语言(函数栈帧的创建和销毁)目录

 

一、什么是函数栈帧

 1.1   函数栈帧:

1.2  栈:

 1.3寄存器:

   1.3.1  寄存器的概念是:

1.3.2寄存器的功能:

1.3.3相关的寄存器为:

1.3.4 相关汇编命令:

二、函数栈帧的创建和销毁

2.1首先了解运行时栈堆的使用:

2.2 代码演示栈帧创建销毁:

2.2.1首先对这个代码进行调试:​编辑

2.2.2 代码反汇编:

2.2.3函数栈帧的创建:

2.2.4函数栈帧的销毁:

三、总结:


一、什么是函数栈帧

 1.1   函数栈帧:

        1.1.1  函数被调用时,系统会在栈区为该函数开辟一块栈空间,这个栈空间就是该函数的函数栈帧。以main函数的调用为例,main函数是被 invoke_main函数调用的,nvoke_main函数被那个函数调用我们先不去了解。当main函数被调用时就会在栈区为其开辟一块空间来用于main函数的执行,而当main函数结束时这块空间将会进行销毁,这个过程就是函数栈帧的创建和销毁的初步了解和认识。

1.2  栈:

    1.2.1  学习栈帧前我们先了解一下栈 栈的概念是:栈(stack)又名堆栈,它是一种运算受限的线性表。限定仅在表尾进行插入和删除操作的线性表。这一端被称为栈顶,相对地,把另一端称为栈底。向一个栈插入新元素又称作进栈、入栈或压栈,它是把新元素放到栈顶元素的上面,使之成为新的栈顶元素;从一个栈删除元素又称作出栈或退栈,它是把栈顶元素删除掉,使其相邻的元素成为新的栈顶元素。
     1.2.2  栈 通俗的理解可以理解为我们所住的宾馆,当我们需要的时候就入住,而入住时间结束则需要退房,所以在计算机里面栈就是指数据暂时存储的地方,所以才有进栈、出栈的说法。
    1.2.3 在计算机系统中,栈则是一个具有以上属性的动态内存区域。程序可以将数据压入栈中,也可以将数据 从栈顶弹出。压栈操作使得栈增大,而弹出操作使得栈减小。 在经典的操作系统中,栈总是向下增长(由高地址向低地址)的。 在我们常见的i386 或者 x86-64 下,栈顶由成为 esp 的寄存器进行定位的。
c语言(函数栈帧的创建和销毁)

 1.3寄存器:

~同样的我们也需要了解一下什么是寄存器。

   1.3.1  寄存器的概念是

       1、寄存器是CPU内部用来存放数据的一些小型存储区域,用来暂时存放参与运算的数据和运算结果。其实寄存器就是一种常用的时序逻辑电路,但这种时序逻辑电路只包含存储电路。寄存器的存储电路是由锁存器或触发器构成的,因为一个锁存器或触发器能存储1位二进制数,所以由N个锁存器或触发器可以构成N位寄存器。寄存器是中央处理器内的组成部分。寄存器是有限存储容量的高速存储部件,它们可用来暂存指令、数据和位址。

1.3.2寄存器的功能:

①清除数码:将寄存器里的原有数码清除。

②接收数码:在接收脉冲作用下,将外输入数码存入寄存器中。

③存储数码:在没有新的写入脉冲来之前,寄存器能保存原有数码不变。

④输出数码:在输出脉冲作用下,才通过电路输出数码。

1.3.3相关的寄存器为

① eax:通用寄存器,保留临时数据,常用于返回值。
② ebx:通用寄存器,保留临时数据。
③ ebp:栈底寄存器。
④ esp:栈顶寄存器。

⑤ eip:指令寄存器,保存当前指令的下一条指令的地址。

1.3.4 相关汇编命令:

1、mov :数据转移指令
2、push :数据入栈,同时 esp 栈顶寄存器也要发生改变
3、pop :数据弹出至指定位置,同时 esp 栈顶寄存器也要发生改变
4、sub:减法命令
5、add:加法命令
6、call :函数调用, 1 . 压入返回地址 2. 转入目标函数
7、jump :通过修改 eip ,转入目标函数,进行调用
8、ret :恢复返回地址,压入 eip ,类似 pop eip 命令

二、函数栈帧的创建和销毁

2.1首先了解运行时栈堆的使用:

c语言(函数栈帧的创建和销毁)

函数被调用时,系统会在栈区为该函数开辟一块栈空间,这个栈空间就是该函数的函数栈帧,而以用到的寄存器esp 和 ebp , ebp 记录的是栈底的地址, esp 记录的是栈顶 的地址。

2.2 代码演示栈帧创建销毁:

#include <stdio.h> 
int Add(int x, int y)
 { int z = 0; z = x + y; return z; }
int main() 
{ 
   int a = 3;
   int b = 5; 
   int ret = 0; 
   ret = Add(a, b);
   printf("%d\n", ret);
   return 0; 
}

 代码是很简单的主要是为了我们来进行深入的了解栈帧的创建和销毁。

2.2.1首先对这个代码进行调试:c语言(函数栈帧的创建和销毁)

      我们刚开始就已经说啦,main函数是被invoke_main函数调用的,invoke_main函数被那个函数调用我们先不去了解。但函数栈帧的创建和销毁过程,在不同的编译器上实现的方法大同小异,可能不同的编译器main调用函数不一样但是方法都是相同的。这样我们就可以知道invoke_main函数肯定存在自己的栈帧,相同的main函数和Add函数也会存在自己的栈帧,每个函数栈帧都有自己的 ebp esp 来维护栈帧空间。

2.2.2 代码反汇编:

c语言(函数栈帧的创建和销毁)

2.2.3函数栈帧的创建:

  上面代码的反汇编们来帮助我们进行理解 

00 BE1820    push           ebp         // ebp 寄存器中的值进行压栈,此时的 ebp 中存放的是
invoke_main 函数栈帧的 ebp esp-4
00 BE1821    mov            ebp , esp          //move 指令会把 esp 的值存放到 ebp 中,相当于产生了 main 函数的 ebp,这个值就是 invoke_main 函数栈帧的 esp
00 BE1823    sub             esp , 0E4 h         //sub 会让 esp 中的地址减去一个 16 进制数字 0xe4, 产生新的esp,此时的 esp main 函数栈帧的 esp ,此时结合上一条指令的 ebp 和当前的 esp ebp esp 之间维护了一 个块栈空间,这块栈空间就是为main 函数开辟的,就是 main 函数的栈帧空间,这一段空间中将存储 main 函数 中的局部变量,临时数据已经调试信息等。
00 BE1829    push           ebx // 将寄存器 ebx 的值压栈, esp-4
00 BE182A    push           esi // 将寄存器 esi 的值压栈, esp-4
00 BE182B push edi // 将寄存器 edi 的值压栈, esp-4
// 上面 3 条指令保存了 3 个寄存器的值在栈区,这 3 个寄存器的在函数随后执行中可能会被修改,所以先保存寄
存器原来的值,以便在退出函数时恢复。
// 下面的代码是在初始化 main 函数的栈帧空间。
//1. 先把 ebp-24h 的地址,放在 edi
//2. 9 放在 ecx
//3. 0xCCCCCCCC 放在 eax
//4. 将从 edp-0x2h ebp 这一段的内存的每个字节都初始化为 0xCC
00 BE182C    lea                edi ,[ ebp - 24 h ]
00 BE182F    mov              ecx , 9
00 BE1834    mov              eax , 0 CCCCCCCCh
  c语言(函数栈帧的创建和销毁)

 这里我们知道一个小的知识点:

     为什么局部变量的时候一定要初始化,因为若无初始化系统在内存中初始化都为0xCC,产生的随机值我们是不知道的,所以一定要初始化你定的数。

                 int a = 3;

00 BE183B            mov              dword ptr [ ebp - 8 ], 3 // 3 存储到 ebp-8 的地址处, ebp-8 的位置其就是 变量
                int b = 5 ;
00 BE1842            mov              dword ptr [ ebp - 14 h ], 5 // 5 存储到 ebp-14h 的地址处, ebp-14h 的位置 其实是b 变量
                int ret = 0 ;
00 BE1849            mov              dword ptr [ ebp - 20 h ], 0 // 0 存储到 ebp-20h 的地址处, ebp-20h 的位 置其实是ret 变量
// 以上汇编代码表示的变量 a,b,ret 的创建和初始化,这就是局部的变量的创建和初始化
// 其实是局部变量的创建时在局部变量所在函数的栈帧空间中创建的
// 调用 Add 函数
                ret = Add ( a , b );
// 调用 Add 函数时的传参
// 其实传参就是把参数 push 到栈帧空间中
00 BE1850            mov                eax , dword ptr [ ebp - 14 h ]          // 传递 b ,将 ebp-14h 处放的 5 放在 eax 寄存器中
00 BE1853           push                eax          // eax 的值压栈, esp-4
00 BE1854           mov                 ecx , dword ptr [ ebp - 8 ]          // 传递 a ,将 ebp-8 处放的 3 放在 ecx 寄存器中
00 BE1857           push                ecx          // ecx 的值压栈, esp-4
//跳转调用函数
00 BE1858           call                  00 BE10B4
00 BE185D          add                 esp , 8
00 BE1860          mov                 dword ptr [ ebp - 20 h ], eax

c语言(函数栈帧的创建和销毁)

Add的传参 : 

        //调用 Add 函数
        ret = Add ( a , b );
        //调用 Add 函数时的传参
        //其实传参就是把参数 push 到栈帧空间中,这里就是函数传参
00 BE1850         mov         eax , dword ptr [ ebp - 14 h ]         // 传递 b ,将 ebp-14h 处放的 5 放在 eax 寄存器中
00 BE1853         push         eax          // eax 的值压栈, esp-4
00 BE1854         mov         ecx , dword ptr [ ebp - 8 ]          // 传递 a ,将 ebp-8 处放的 3 放在 ecx 寄存器中
00 BE1857         push         ecx          // ecx 的值压栈, esp-4
// 跳转调用函数
00 BE1858         call         00 BE10B4
00 BE185D         add         esp , 8
00 BE1860         mov         dword ptr [ ebp - 20 h ], eax

 

c语言(函数栈帧的创建和销毁)

 函数调用过程:

00 BE1858         call          00 BE10B4
00 BE185D         add         esp , 8
00 BE1860         mov         dword ptr [ ebp - 20 h ], eax
        call 指令是要执行函数调用逻辑的,在执行 call 指令之前先会把 call 指令的下一条指令的地址进行压栈操作,这个操作是为了解决当函数调用结束后要回到call 指令的下一条指令的地方,继续往后执行。
Add函数的反汇编:

 int Add(int x, int y)

{
00 BE1760         push         ebp          // main 函数栈帧的 ebp 保存 ,esp-4
00 BE1761         mov         ebp , esp          // main 函数的 esp 赋值给新的 ebp ebp 现在是 Add 函数的 ebp
00 BE1763         sub         esp , 0 CCh          // esp-0xCC ,求出 Add 函数的 esp
00 BE1769         push         ebx          // ebx 的值压栈 ,esp-4
00 BE176A         push         esi          // esi 的值压栈 ,esp-4
00 BE176B         push         edi         // edi 的值压栈 ,esp-4
int z = 0 ;
00 BE176C         mov         dword ptr [ ebp - 8 ],           // 0 放在 ebp-8 的地址处,其实就是创建 z
z = x + y ;
// 接下来计算的是 x+y ,结果保存到 z
00 BE1773         mov         eax , dword ptr [ ebp + 8 ] // ebp+8 地址处的数字存储到 eax
00 BE1776         add         eax , dword ptr [ ebp + 0 Ch ] // ebp+12 地址处的数字加到 eax 寄存中
00 BE1779         mov         dword ptr [ ebp - 8 ], eax          // eax 的结果保存到 ebp-8 的地址处,其实 就是放到z
return z ;
00 BE177C         mov         eax , dword ptr [ ebp - 8 ]          // ebp-8 地址处的值放在 eax 中,其实就是把z 的值存储到 eax 寄存器中,这里是想通过 eax 寄存器带回计算的结果,做函数的返回值。
}
00 BE177F         pop         edi
00 BE1780         pop         esi
00 BE1781         pop         ebx
00 BE1782         mov         esp , ebp
00 BE1784         pop         ebp
00 BE1785         ret
    代码执行到Add函数的时候,就要开始创建Add函数的栈帧空间了。
 在Add函数中创建栈帧的方法和在main函数中是相似的,在栈帧空间的大小上略有差异而已。就不再进行演示啦。

2.2.4函数栈帧的销毁:

1、要知道我们创建的栈就好比我们住的宾馆,当不用的时候要退房间,栈也一样,当调用完成后要进行销毁。而如何销毁呢我们可以看一下。

00BE177F         pop         edi         //在栈顶弹出一个值,存放到edi中,esp+4

00 BE1780         pop         esi          // 在栈顶弹出一个值,存放到 esi 中, esp+4
00 BE1781         pop         ebx          // 在栈顶弹出一个值,存放到 ebx 中, esp+4
00 BE1782         mov         esp , ebp          //再将 Add 函数的 ebp 的值赋值给 esp ,相当于回收了 Add 函数的栈帧空间
00 BE1784         pop         ebp         // 弹出栈顶的值存放到 ebp ,栈顶此时的值恰好就是 main 函数的 ebp, esp+4,此时恢复了 main 函数的栈帧维护, esp 指向 main 函数栈帧的栈顶, ebp 指向了 main 函数栈帧的栈底。
00 BE1785         ret          //ret 指令的执行,首先是从栈顶弹出一个值,此时栈顶的值就是 call 指 令下一条指令的地址,此时esp+4 ,然后直接跳转到 call 指令下一条指令的地址处,继续往下执行。
c语言(函数栈帧的创建和销毁)
 
但调用完 Add 函数,回到 main 函数的时候,继续往下执行,可以看到:
 
00 BE185D         add         esp , 8 //esp 直接 +8 ,相当于跳过了 main 函数中压栈的 a'和 b'
00 BE1860         mov         dword ptr [ ebp - 20 h ], eax // eax 中值,存档到 ebp-0x20 的地址处,其实就是存储到main 函数中 ret 变量中,而此时 eax 中就是 Add 函数中计算的 x y 的和,可以看出来,本次函数的返回值是由eax 寄存器带回来的。程序是在函数调用返回之后,在 eax 中去读取返回值的。

 到这里函数栈帧的创建和销毁就结束了。

三、总结:

学完想必我们就好理解一下几个问题啦:文章来源地址https://www.toymoban.com/news/detail-466391.html

1、局部变量是如何创建的?在申请栈帧空间的时候,会存在一小部分空间来存放我们的局部变量
2、为什么局部变量不初始话的值是随机的?因为若无初始化系统在内存中初始化都为0xCC,产生的随机值我们是不知道的,所以一定要初始化你定的数。
3、函数是怎么传参的?再没有传参之前我们就通过寄存器指令就已经把函数的形参已经传入寄存器中,等调用Add函数时开辟新的栈帧空间时我们再将形参形参传入进去。
4、形参和实参是什么关系?形参是我们在压榨的时候形成的,和我们实参值是相同的,但空间是独立的。所以形参只是我们实参的临时拷贝,改变形参不会影响实参的结果。
5、最后孩子码文不易,如果觉得内容有用,希望大家点赞收藏,一同学习进步!!!!

到了这里,关于c语言(函数栈帧的创建和销毁)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索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日
    浏览(38)
  • 从汇编代码探究函数栈帧的创建和销毁的底层原理

    人,只有在放弃战斗的时候才算输,只要坚持战斗,就还没输  本文收录于青花雾气-计算机基础 往期回顾 从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

领红包