函数的栈帧

       我们每次在调用函数的时候,都说会进行传参。每次创建函数,或者进行递归的时候,也会说会进行压栈。

       那么,今天我们就来具体看看函数到底是如何进行压栈,传参的操作。

什么是栈?

       首先我们要知道,我们将内存一般划分为三个区域:

  • 静态区
  • 堆区
  • 栈区

       我们平时创建的临时变量,函数都会在栈区中占据空间:

       此时我们也要知道栈区的使用规则:从高地址向低地址使用

栈的使用规则:

       我们知道抢的弹夹,我们要逐个把子弹往里面压,之后如果取出子弹,就需要将上一次压入的子弹取出,之后逐个取出子弹,并只能按照顺序取出。

       栈就是这样的使用规则,遵循先进后出,后进先出。        此时你会想,不能把任意的数据取出,必须一个一个拿,这种结构真的好用吗?

        起初我也这样认为,但是计算机就喜欢用这种结构。

        在内存中,栈区的使用规则是从高地址向低地址使用的。

函数的栈帧:

       C语言中,我们要想观察函数栈帧就需要用到调试。当我们调试时所在的函数(此时函数未运行完),每个栈帧对应着一个未运行完的函数。栈帧中保存了该函数的返回地址和局部变量。

       因为我们知道,只要运行函数就会进行压栈操作,所以分析出一下信息:

  1. 栈帧是一块因函数运行而创建的的临时空间。
  2. 每调用一次函数都会创建一个独立的函数栈帧。
  3. 栈帧中存放着函数重要信息,如局部变量,函数返回地址,函数参数等。
  4. 当函数运行完毕后栈帧会销毁。

       既然会创建函数栈帧,那么就会维护其空间,计算机使用寄存器维护空间。

什么是寄存器?

       这里牵扯很多内容,我们只给出笼统解释:寄存器是集成到CPU上的,是独立的,寄存器可以暂存指令,地址和数据。所以寄存器也可以理解为指针。

       我们会使用很多寄存器,要理解清楚函数栈帧,就必须理解ebp和esp,这两个寄存器中存放的是地址,这两个地址是用来维护函数栈帧的。我们详细来讲esp和ebp两个寄存器。其余寄存器混个脸熟即可,一会会用到。

esp寄存器:

       维护栈顶,始终指向栈顶(此时esp寄存器存储栈顶地址)。

ebp寄存器:

       维护当前函数栈帧的栈低(此时ebp寄存器存储函数栈帧栈低地址)。

几个必要的汇编指令:

       我们观察函数栈帧的创建和销毁,就要知道几个汇编指令,这样可以更好的阅读以下内容。

       记不住没关系,我们一下会一一讲解。 

图解: 

        这里我们使用VS2013来观察,由于VS2022太过高级,有些内部细节就会看不到,所以用VS2013来观察。此时我们执行以下代码:

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;
}

       esp和ebp就是维护当前调用的函数。 

       点击F10开始调试。       接下来我们看main函数被谁调用了,我们进入main函数,并直接执行完。

       在VS2013中,main函数也是被其他函数调用的。没想到main函数也是被调用的函数,也理解了为什么每次都要有返回值。 

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

       之后我们按住F10,之后右击鼠标找到“转到反编汇”,就可以找到C语言所对应的汇编代码,箭头的指向就可以一行一行的执行,我们来逐过程分析:

       

        push ebp,将ebp压入栈区。因为esp维护栈顶,所以esp指向改变。我们可以观察其存放地址的改变。

       之后执行move,move是把后面的值赋到前面去。

       此时ebp和esp指向的位置相同。

        之后,就要创建函数的栈帧了,执行sub,就是将其寄存器存放地址减去一个地址,因为栈区是高地址到底地址,所以该地址向上。

 

       此时esp维护main函数的栈顶,ebp维护main函数的栈低。我们可以看内存:

       这些内存都是为main函数开辟的空间。

       之后有执行了3次push,push时会有一个动作,就是栈顶指针esp会变,一直指向栈顶。

       我们通过内存窗口来观察:        因为我们说过,寄存器既可以存地址,也可以存数据,此时ebx存的是数据(就是内存里面的内容),而esp存放的是ebx的地址。压入ebx以后,继续将esi、edi压入。

       之后执行lea : load effective address 加载有效地址。        此时会发现,正好是加载的空间正好是最开始esp减去的空间,就是main函数栈帧的低地址。

       之后执行的命令,我们就需要先讲解一下了。

       我们应该听过字节的概念,1字节等于8比特位,那么字和字节又什么关系?一个字等于两个字节。

       比特记为bit,字节记为Byte,字记为word,所以有如下关系:

  • 1Byte=8bits
  • 1word=2Bytes=16bits 
  • dword:一个word是两个字节,d代表double,就是双字,就是4个字节。

       此时我们要看其以下的三个步骤:

        此时edi里面存放main函数栈帧的低地址。

        这样就可以理解为什么每次打印未初始化的空间,打印出来的字符都是一个汉字“烫烫烫烫”了。

       此时才会开始执行有效的代码。在此之前都是为main函数开辟的空间。

       此时就要调用Add函数了,一样的,我们要改变ebp和esp的指向,因为进入Add函数就需要维护Add函数栈帧了,但是还是要做以下准备,就是传参,我们来看形式参数的创建。

       这两个动作相当于传参,之后执行call,就是调用函数,要记住call的地址,此时点击F11才能进入Add函数。

       我们可以发现就在ecx的下一个地址里面存储了call指令下一个执行的地址。为什么要记录地址?我们先埋个伏笔,此时我们会先进入Add函数,流程如下:

       注意此时main函数的函数栈帧已经增长到call指令的下一个地址了。        此时我们来观察Add函数的细节:将esp减去一个地址改变指向:

       之后还是main函数栈帧的那一套操作,压入3个寄存器并初始化空间,并将z初始化为0: 

       此时先将 ebp + 8 的值赋给 eax ,此时 eax = 10;之后又执行add,将 ebp + 12 的值等于30,最后将eax的值赋给 ebp - 8 ,此时 ebp - 8 地址的值是30.        我们可以发现,我们使用Add函数并没有创建形参,在我们传参时其实已经压栈过了,而且参数是从右向左传参的。

       返回的话z会被销毁,我们来观察其如何返回。

       我们将结果放入eax寄存器当中,此时就不用担心函数销毁。

       此时将上面的3个寄存器弹出栈顶。

       之后mov esp的位置,esp的指向改变:        此时弹出ebp,ebp弹出以后会指向main函数的栈低,因为之前记录着mian函数的栈低。

 

        当前栈顶元素为call指令的下一个指令的地址,ret这条指令就是找到之前call指令记录的地址,并pop一次栈顶元素。

        此时执行add   esp,8 因为没有dword 所以是改变指向。

       此时将形参x,y的空间还给操作系统。此时又执行mov,将eax存放的值赋给 ebp - 20h 就是给c赋值。 

       此时main函数执行完,也是以上步骤,我们不再赘述。

总结: 

       我们通过观察函数栈帧的创建和销毁,最后返回值是由寄存器带回来的;也可以理解为什么局部变量的值是随机的,形参和实参的关系,确实是一份临时拷贝。希望大家下去多加练习,逐渐就会顿悟其中的原理。

       爆肝一整天,点点赞吧,呜呜~

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

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

相关文章

Error opening file for writing报错解决

报错展示及描述 在安装pycharm的时候出现了一下报错, Error opening file for writing。 报错原因 一般出现这种报错都是文件权限的原因,检查一下,果然这个文件夹权限是【只读】 查看文件权限的方式:【右击】文件夹名称&#xff0…

046:vue通过axios调用json地址数据的方法

第046个 查看专栏目录: VUE ------ element UI 专栏目标 在vue和element UI联合技术栈的操控下,本专栏提供行之有效的源代码示例和信息点介绍,做到灵活运用。 (1)提供vue2的一些基本操作:安装、引用,模板使…

ffmpeg过滤器filter理论与实战

文章目录 前言一、DirectShow1、简介2、程序基本结构3、架构 二、过滤器1、视频过滤器 -vf2、音频过滤器 -af3、过滤器链(Filterchain)4、过滤器图(Filtergraph)①、基本语法②、Filtergraph 的分类 5、结构体间的关系图 三、过滤…

保研毕业论文查重率多少通过【保姆教程】

大家好,今天来聊聊保研毕业论文查重率多少通过,希望能给大家提供一点参考。 以下是针对论文重复率高的情况,提供一些修改建议和技巧: 保研毕业论文查重率多少通过 在保研过程中,毕业论文的查重率是衡量学术诚信和论文…

JAVA8新特性之函数式编程详解

JAVA8新特性之函数式编程详解 前言一、初步了解函数式接口二、 Lambda表达式2.1 概述2.2 lambda省略规则2.3 lambda省略常见实例2.4 lambda表达式与函数式接口 三、 Stream流3.1 stream流的定义3.2 Stream流的特点3.3 Stream流的三个步骤3.4 Stream 和 Collection 集合的区别&a…

【HarmonyOS开发】拖拽动画的实现

动画的原理是在一个时间段内,多次改变UI外观,由于人眼会产生视觉暂留,所以最终看到的就是一个“连续”的动画。UI的一次改变称为一个动画帧,对应一次屏幕刷新,而决定动画流畅度的一个重要指标就是帧率FPS(F…

【带头学C++】----- 九、类和对象 ---- 9.12 C++之友元函数(9.12.1---12.4)

❤️❤️❤️❤️❤️❤️❤️❤️❤️❤️❤️创做不易,麻烦点个关注❤️❤️❤️❤️❤️❤️❤️❤️❤️❤️❤️❤️ ❤️❤️❤️❤️❤️❤️❤️❤️❤️文末有惊喜!献舞一支!❤️❤️❤️❤️❤️❤️❤️❤️❤️❤️ 目录 9.12…

五:爬虫-数据解析之xpath解析

三:数据解析之xpath解析 1.xpath介绍: ​ xpath是XML路径语言,它可以用来确定xml文档中的元素位置,通过元素路径来完成对元素的查找,HTML就是XML的一种实现方式,所以xpath是一种非常强大的定位方式​ XPa…

vue2 element-ui select下拉框 选择传递多个参数

<el-select v-model"select" slot"prepend" placeholder"请选择" change"searchPostFn($event,123)"> <el-option :label"item.ziDianShuJu" :value"{value:item.id, label:item.ziDianShuJu}" v-for&qu…

Ubuntu系统使用快速入门实践(七)——软件安装与使用(5)

Ubuntu系统使用快速入门实践系列文章 下面是Ubuntu系统使用系列文章的总链接&#xff0c;本人发表这个系列的文章链接均收录于此 Ubuntu系统使用快速入门实践系列文章总链接 下面是专栏地址&#xff1a; Ubuntu系统使用快速入门实践系列文章专栏 文章目录 Ubuntu系统使用快速…

【unity】【WebRTC】从0开始创建一个Unity远程媒体流app-构建可同步场景

【背景】 最近在研究远程画面&#xff0c;所以就实践了一下。技术采用我认为比较合适的WebRTC。 这篇文章的基础是我的另一篇博文&#xff0c;如果希望顺利完成本篇操作&#xff0c;请先关注我后查询我的如下博文&#xff1a; 【WebRTC】【Unity】Unity Web RTC1-Unity中简单实…

Docker架构及常用的命令

一、初识Docker 1、 docker是一个快速交付应用、运行应用的技术&#xff0c;具备下列优势&#xff1a; 可以将程序及其依赖、运行环境一起打包为一个镜像&#xff0c;可以迁移到任意Linux操作系统运行时利用沙箱机制形成隔离容器&#xff0c;各个应用互不干扰启动、移除都可以…

边缘智能网关如何应对环境污染难题

随着我国工业化、城镇化的深入推进&#xff0c;包括大气污染在内的环境污染防治压力继续加大。为应对环境污染防治难题&#xff0c;佰马综合边缘计算、物联网、智能感知等技术&#xff0c;基于边缘智能网关打造环境污染实时监测、预警及智能干预方案&#xff0c;可应用于大气保…

银行数据分析入门篇:信用卡全生命周期分析,到底应该怎么做?

最近有朋友向我咨询银行信贷业务的数据分析&#xff0c;就看了很多案例&#xff0c;刚好看到一个信用卡全生命周期分析的案例&#xff0c;做得很详细又通俗易懂&#xff0c;基本上可以直接复制套用&#xff0c;所以特地分享给大家。 本文主要分享作者整个分析作品的思路&#x…

106.进程控制(结束、孤儿、僵尸进程)以及进程回收

目录 结束进程 孤儿进程 僵尸进程 进程回收 wait() waitpid 进程控制是指在操作系统中对进程进行创建、终止、挂起、唤醒以及进程之间的同步、通信等操作的管理。 结束进程 exit() 和 _exit() 函数都用于终止一个进程&#xff0c;但它们之间有一些重要的区别&#xf…

新工科:数据科学与大数据技术实验中心解决方案,赋能高校新工科数智人才培养

随着数字经济蓬勃发展&#xff0c;数字化产业和产业数字化成为就业增长新动能。据人瑞人才与德勤调研显示&#xff0c;未来3年&#xff0c;数字产业化企业最需要运营人员和开发人员&#xff08;包括大数据开发工程师、数据建模开发工程师等&#xff09;&#xff0c;其次是数据分…

【RTOS学习】FreeRTOS中的链表 | 堆的管理

&#x1f431;作者&#xff1a;一只大喵咪1201 &#x1f431;专栏&#xff1a;《RTOS学习》 &#x1f525;格言&#xff1a;你只管努力&#xff0c;剩下的交给时间&#xff01; 目录 &#x1f969;FreeRTOS中的链表&#x1f95e;初始化&#x1f95e;尾部插入&#x1f95e;按顺…

OpenWRT搭建本地web站点并结合内网穿透实现公网远程访问

文章目录 前言1. 检查uhttpd安装2. 部署web站点3. 安装cpolar内网穿透4. 配置远程访问地址5. 配置固定远程地址 前言 uhttpd 是 OpenWrt/LuCI 开发者从零开始编写的 Web 服务器&#xff0c;目的是成为优秀稳定的、适合嵌入式设备的轻量级任务的 HTTP 服务器&#xff0c;并且和…

【Windows】MCSM面板搭建Mycraft服务器,实现公网远程联机

文章目录 前言1.Mcsmanager安装2.创建Minecraft服务器3.本地测试联机4. 内网穿透4.1 安装cpolar内网穿透4.2 创建隧道映射内网端口 5.远程联机测试6. 配置固定远程联机端口地址6.1 保留一个固定TCP地址6.2 配置固定TCP地址 7. 使用固定公网地址远程联机 前言 MCSManager是一个…

[香橙派]Orange pi zero 3命令行配网方法——建立ssh连接——Ubuntu配置WIFI自动连接

一、前言 前面我们给Orange Pi安装了Ubuntu系统&#xff0c;并通过MobaXterm进行了串口连接&#xff0c;但其实并不方便&#xff0c;在日常开发中&#xff0c;我们希望能够使用更方便的ssh连接来进行操作&#xff0c;因此配置网络是必要的。 本章介绍的方法无需网线、HDMI线等&…