【C语言】9.C语言函数栈帧的创建和销毁

C语言函数栈帧的创建和销毁


看完本文你能了解什么?

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

寄存器

我们常用的寄存器有eax,ebx,ecx,edx。不过我们这节主要用到的是ebpesp这两个寄存器。

函数栈帧

ebpesp这两个寄存器中存放的是地址。这两个地址是用来维护函数栈帧的。

每一个函数调用,都要在栈区创建一个空间。

在这里插入图片描述

假如说要调用main函数就要在栈区创建一个空间

在这里插入图片描述

这个空间是为main函数开辟的,那么我们就称这个空间是为main函数开辟的一块函数栈帧。

这个空间是ebpesp这两个寄存器来维护的。

在这里插入图片描述

ebp作为寄存器存储的是如图所指向的地址。(寄存器是一块区域,是一个存储空间)

esp作为寄存器存储的是如图所指向的地址。

ebpesp中间的这块空间是由这两个寄存器来维护的。

一般我们把ebp叫做栈底指针,把esp叫做栈顶指针。

为啥叫这个呢?

实际上我们可以把这个看成一个栈,从高地址向低地址是不是那个地址在被使用消耗啊,如果想使用地址只能往上使用,就像栈一样,只能往栈顶放数据。


在vs2013中,main函数也是被其他函数调用的。

main函数被__tmainCRTStartup函数调用,而__tmainCRTStartup函数被mainCRTStartup函数调用。

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("%c\n", c);return 0;
}

我们调用main函数,会在栈区分配空间。然后往下走,走到c = Add(a, b);又去调用Add函数去了,给Add函数在栈区分配空间。

这个时候ebpesp这两个指针就去维护Add函数去了。

在这里插入图片描述

那么按照道理,__tmainCRTStartupmainCRTStartup函数在被调用的时候也会有一个对应的函数栈帧。

在这里插入图片描述

右击鼠标转到反汇编

在这里插入图片描述

在调用main函数前,会创建一个__tmainCRTStartup函数栈帧

在这里插入图片描述

进入main函数第一步就是push,也就是压栈。

在这里插入图片描述

压栈操作后,就像这样:

在这里插入图片描述

__tmainCRTStartup函数栈帧上面出现了一个ebp元素。同时esp指针也指向ebp元素的上面了。

下一步是mov,把esp的值给ebp

在这里插入图片描述

这个时候espebp指向了同一个地方

在这里插入图片描述

然后下一步是sub,给esp减去0E4h

在这里插入图片描述

因为下面是高地址,上面是低地址,所以减就意味着esp在图上往上走。espebp之间的那个新的空间就是main函数的栈帧空间。

在这里插入图片描述

然后进行3次push,给顶上压上3个元素。

在这里插入图片描述

然后esp也依次指向ebx上面,esi上面,edi上面。停在edi上面。

在这里插入图片描述

然后进入lea,这里的[]里面的一坨东西看着很费劲

在这里插入图片描述

我们把显示符号名勾上就会变成这样

在这里插入图片描述

ebp-0E4h放到edi里面去,后面两个mov也是把39h放到ecx里面,把0CCCCCCCCh放到eax里面去。

接下来一步比较重要

在这里插入图片描述

这一步是把刚刚edi往下的39h这么多个dword(一个word两个字节,dword是double word四个字节)个数据改成0CCCCCCCCh

压栈(push):给栈顶放一个元素。

出栈(pop):从栈顶删除一个元素。

然后进入mov,把0Ah(也就是10)给到ebp-8的位置上。

在这里插入图片描述

我们假设一个紫色框代表4个字节

在这里插入图片描述

在这里插入图片描述

我们把a=10放进了ebp-8里面,原来的CCCCCCCC就被10替换了。

这也可以说明我们没有初始化的时候内存里面放的就是CCCCCCCC ,之前我们有一节里面打印字符数组没有加/0,导致的打印烫烫烫烫烫烫烫烫就是这个原因。

然后进入mov,把14h放到ebp-14h(相当于ebp-20)上。

在这里插入图片描述

在这里插入图片描述

c的创建也类似,然后我们创建好abc之后,我们调用Add函数。

在这里插入图片描述

我们进入mov,把ebp-14heax。这个ebp-14h不就是b吗?也就是相当于把20给到eax里面去了。

然后push,eax压栈。eax里面放的是20,实际上把20给放进去了。

在这里插入图片描述

然后mov,把ebp-8(也就是a的值10)给了ecx

接着push,ecx压栈。ecx里面放10。

在这里插入图片描述

上面两步其实是传参。

接下来call,就是调用,

在这里插入图片描述

执行call指令会把这个00C21450(也就是call指令的下一条指令的地址)给压栈。

在这里插入图片描述

为什么要压栈这个呢?因为我们call指令执行了之后会进入被调用函数的内部,进去了怎么出来呢?这就要靠call指令的下一条指令了。回来的时候就会找到这个地址,然后从这个地址往下执行。

然后就进入Add函数里面了。

在这里插入图片描述

进入push,把ebp压栈。

在这里插入图片描述

然后mov,把esp的值给ebp。

在这里插入图片描述

然后sub,给esp-0CCh

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

然后继续3次push,3次压栈

在这里插入图片描述

在这里插入图片描述

然后lea,把ebp+FFFFFF34h加到edi里面去。

在这里插入图片描述

然后把33h,放到ecx里面,把0CCCCCCCCh放到eax里面去

然后rep,从edi这个位置开始,向下33h的内容初始化成0CCCCCCCCh

然后进入mov,把0给ebp-8。相当于z=0。

在这里插入图片描述

在这里插入图片描述

然后mov,把ebp+8的值放到eax里面。eax=a=10。

接着add,把ebp+0Ch(也就是ebp+12)的值加到eax里面。eax=10+b=10+20=30。

然后mov,把eax的值放到ebp-8里面。也就是说z从0变成了30。

在这里插入图片描述

在这里插入图片描述

从上面我们可以看到,函数调用的时候没有主动创建x,y这两个形参,而是直接把a,b的参数调用过去了。

传参把a,b的值进行了压栈。

在这里插入图片描述

我们函数传参,还没有调用Add函数的时候我们就先把a,b传过去了,压栈了两个参数。

然后进入Add函数内部进行计算的时候,我们就把压栈的两个参数进行运算。把这两个参数加起来放到z里面去。

所以我们常说,形参是实参的一份拷贝。

然后我们继续mov,把ebp-8的值放到eax(eax是个寄存器)里面去。

我们常说这个zreturn z;在函数调用完就销毁了,销毁了怎么传值出来的呢?就是靠的寄存器传递的,放到这样一个全局的寄存器里面就安全了。

在这里插入图片描述

接下来这个3个pop,就相当于出栈3次。

在这里插入图片描述

在这里插入图片描述

然后我们z的值都给寄存器了,那么Add函数的函数栈帧按道理来说也因该被释放掉了。

然后进入mov,把ebp赋值给esp

在这里插入图片描述

在这里插入图片描述

然后pop,把ebp出栈。这个棕色框就出栈了。然后ebp指针指向的值就是下面的ebp区域了。

在这里插入图片描述

ebp出栈了之后,这个ebp指针指向了下面,因为这个出栈的ebp是为main函数创建的。

在这里插入图片描述

然后esp指针顺势指向了00C21450

在这里插入图片描述

也就相当于我们回到了main函数的函数栈帧里面。

然后是ret。ret指令会让子程序返回到调用函数的代码处。但问题是它怎么知道回到哪里去呢?

在这里插入图片描述

我们注意,现在的栈顶是00C21450,也就是上面说的call指令的下一条指令的地址。ret指令返回的时候其实就是在栈顶返回了call指令的下一条指令的地址。这样才能返回之前的地方。ret后00C21450相当于也pop出去了。

在这里插入图片描述

在这里插入图片描述

然后add,esp+8

在这里插入图片描述

在这里插入图片描述

然后mov,把eax的值给ebp-20h

在这里插入图片描述

也就是把之前Add函数存在eax寄存器里面的返回值给c。

在这里插入图片描述

在这里插入图片描述

c的值从0变成了30。


答案:

  1. 局部变量是怎么创建的?

    在函数栈帧里面划定一块区域给局部变量。

  2. 为什么局部变量的值是随机值?

    因为局部变量不初始化的时候,值是我们随机放进去的。就像这里面的CCCCCCCC。

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

    还没调用函数的时候就push,push,把这两个要用的实参压栈压进去。然后调用的时候通过指针偏量来调用这两个压栈压进去的值。

  4. 实参和形参是什么关系?

    形参是实参的一份拷贝。但是形参不会影响实参。

  5. 函数调用是怎么做的?

    上面讲过。

  6. 函数调用结束后怎么返回的?

    我们在调用函数之前就把call指令的下一条指令的地址压栈压进去了。当我们调用完函数后,弹出ebp,和call指令下一条指令的地址后,esp指针往下走的时候就可以跳转到call指令下一条指令的地址。返回值是通过寄存器来传递回来的。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/18791.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

LeetCode hot100-57-G

17. 电话号码的字母组合 给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。答案可以按 任意顺序 返回。给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。不会,放IDEA里执行了一下大概理解了流程 …

深圳比创达电子|EMC与EMI滤波器:电子设备的“电磁防护罩”

在电子科技日新月异的今天,电磁兼容性(EMC)问题越来越受到工程师和技术人员的关注。其中,电磁干扰(EMI)和电磁干扰抑制(即EMI滤波器)是实现良好EMC性能的关键技术之一。 一、EMC与E…

KineFX —— 简介

KineFX是绑定和动画的框架和工具集,可在SOP级别创建和编辑角色;可从头创建自己的KineFX角色,或使用特定的KineFX SOP和常规的SOP去编辑导入的角色和动画; 程序化绑定 KineFX构建与程序化绑定的原则上,可快速非破坏性迭…

AI绘画Stable Diffusion【艺术写真】:蒙版法图生图,局部重绘实现AI艺术写真

大家好,我是设计师阿威 之前我分享过几篇使用SD插件换脸方式实现AI写真的教程,主要存在2个大的问题。 (1)人脸相似度 (2)生成的图片整体色调有时候会比较怪异 对于上面的问题,在对图片质量要…

每天写两道(二)LRU缓存、数组中最大的第k个元素

146.LRU 缓存 . - 力扣(LeetCode) 请你设计并实现一个满足 LRU (最近最少使用) 缓存 约束的数据结构。 实现 LRUCache 类: LRUCache(int capacity) 以 正整数 作为容量 capacity 初始化 LRU 缓存int get(int key) 如果关键字 key 存在于缓存…

在table表格中如何给tr的每一个子元素加haver效果

效果图: 核心代码: tbody tr :hover {background-color: #d5d5d5; } 改变子元素 tbody tr:hover {background-color: #d5d5d5; } 改变父元素 两段代码看起来一样,其实不一样,其中差了一个空格字符 希望可以帮到大家

多线程新手村3--多线程代码案例

1.1 单例模式 单例模式是设计模式中非常经典的一种。那么有同学肯定就会好奇了,什么是设计模式呢? 设计模式简单的说就是程序员的“棋谱”,我们下象棋时肯定或多或少都背过棋谱,例如当头炮、马后炮等,设计模式也是这…

接口性能测试复盘:解决JMeter超时问题的实践

在优化接口并重新投入市场后,我们面临着一项关键任务:确保其在高压环境下稳定运行。于是,我们启动了一轮针对该接口的性能压力测试,利用JMeter工具模拟高负载场景。然而,在测试进行约一分钟之后,频繁出现了…

剧本杀小程序开发,探索市场发展新的商业机遇

剧本杀游戏作为一个新兴行业,经历了爆发式的增长,剧本杀游戏在市场中的热度不断升高。 不过,在市场的火热下,竞争也在逐渐加大。因此,在市场竞争下,成本低、主题多样、有趣的线上剧本杀小程序成为了创业者…

竹云董事长在第二届ICT技术发展与企业数字化转型高峰论坛作主题演讲

5月25日,由中国服务贸易协会指导,中国服务贸易协会信息技术服务委员会主办的 “第二届ICT技术发展与企业数字化转型高峰论坛” 在北京隆重召开。 本次论坛以 “数据驱动,AI引领,打造新质生产力” 为主题,特邀业内200余…

WebGL实现医学教学软件

使用WebGL实现医学教学软件是一个复杂但非常有益的项目,可以显著提升医学教育的互动性和效果。以下是详细的实现步骤,包括需求分析、技术选型、开发流程和注意事项。北京木奇移动技术有限公司,专业的软件外包开发公司,欢迎交流合作…

redis-cli help使用

1. redis-cli命令使用—先连接上服务器 连接到 Redis 服务器: 使用 redis-cli 命令即可连接到本地运行的 Redis 服务器,默认连接到本地的 6379 端口。 redis-cli如果 Redis 服务器不在本地或者端口不同,可以使用 -h 和 -p 参数指定主机和端…

探索Django 5: 从零开始,打造你的第一个Web应用

今天我们将一起探索 Django 5,一个备受开发者喜爱的 Python Web 框架。我们会了解 Django 5 的简介,新特性,如何安装 Django,以及用 Django 编写一个简单的 “Hello, World” 网站。最后,我会推荐一本与 Django 5 相关…

苏洵,大器晚成的家风塑造者

💡 如果想阅读最新的文章,或者有技术问题需要交流和沟通,可搜索并关注微信公众号“希望睿智”。 苏洵,字明允,号老泉,生于宋真宗大中祥符二年(公元1009年),卒于宋英宗治平…

PPT忘记保存?教你如何轻松恢复

在日常办公中PPT文件作为主流文档格式,承载着我们大量的工作成果。然而当不小心误点了“不保存”按钮,或是遭遇软件崩溃等意外情况导致文档丢失时,文件内容是否还能够能恢复,往往成为我们最关心的问题。本文将为您提供五大免费且实…

《探索网络七层模型:构建高效通信架构的关键》

在当今数字化时代,网络通信已经成为人们生活和工作中不可或缺的一部分。而网络七层模型作为计算机网络体系结构的重要基础,其技术架构对于构建高效、稳定的通信系统具有重要意义。本文将深入探讨网络七层模型的技术架构设计,以及其在构建现代…

轻松掌握图片批量处理,赶紧学习这些小技巧!

在现今数字化的社会中,我们每天都会接触到大量的图片,无论是在工作中还是日常生活中。要想高效处理这些图片,掌握图片批量处理的技巧就显得尤为重要。幸运的是,有许多小技巧和工具可以让这一过程变得轻松愉快。 在本文中&#xf…

信息学一周赛事安排

本周比赛提醒 本周有以下几场比赛即将开始: 1.ABC-356 比赛时间:6月1日(周六)晚20:00 比赛链接:https://atcoder.jp/contests/abc356 2.ARC-179 比赛时间:6月2日(周日)晚20:00 …

excel导到access后长文本内容缺失

参考该文Excel表格如何导入Access数据库以及列内容超过255个字符被截断解决办法 - 知乎 原因是access数据中列内容超过255个字符会被截断。 解决方法:将该字段的数据类型修改为长文本即可。

微调真的能让LLM学到新东西吗:引入新知识可能让模型产生更多的幻觉

大型语言模型(llm)是在巨大的文本语料库上训练的,在那里他们获得了大量的事实知识。这些知识嵌入到它们的参数中,然后可以在需要时使用。这些模型的知识在培训结束时被“具体化”。在预训练结束时,模型实际上停止学习。 对模型进行对齐或进行…