C++函数栈帧详解

函数栈帧的创建和销毁

在不同的编译器下,函数调用过程中栈帧的创建是略有差异的,具体取决于编译器的实现!

且需要注意的是,越高级的编译器越不容易观察到函数栈帧的内部的实现;

关于函数栈帧的维护这里我们要重点介绍两个寄存器:ebp和esp

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

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

问题:什么是函数栈帧?

函数栈帧实际上就是函数运行时栈上的一块空间!用于存储相对应的临时数据!

接下来讲解以x86系统为例:

这里对函数栈帧管理就是靠这两个寄存器:

  • ebp:被称为基址指针,指向栈帧的底部,(高地址处,且是固定位置!);
  • esp:被称为栈顶指针,指向栈帧的顶部,(低地址处,地址可变);

这里需要注意的是,对于栈来说,是优先使用高地址的(栈的特性!)! 

问题:那么函数的栈帧中都存放了哪些数据?

从低地址到高地址依次存放了:

  • 调用栈帧的基址指针(ebp)--- 用于函数调用后恢复栈帧状态;
  • 下一条指令的地址 --- 执行完该函数后跳转到下一条指令;
  • 被调用的函数的参数(形参) --- 需要注意的是会从右到左进行压栈;
  • 被调用函数的局部变量和临时数据;
  • 寄存器的上下文(例如调用函数期间,使用的ebx、esi、edi等寄存器);

问题:main函数会被其他函数调用吗? 

需要注意的是,main函数也是可以被其他函数调用的:

即__tmainRTStartup会调用main函数!

问题:那么哪个谁调用 __tmainRTStartup这个函数?

__tmainRTStartup会被mainCRTStartup这个函数调用!

所以,这里我们总结一下:

因此,假如说当前我们的main函数里面调用了一个简单的add函数,那么:

中间绿色的框是我们对应的main函数的调用堆栈,而在调用main函数之前,会先调用__tmainRTStartup和mainRTStartup这两个函数!

而add函数在main函数上面,也就是对应的压栈!

问题:但是函数栈帧中具体是怎么进行相关操作的?

示例代码操作

这里我们以一个简单的代码为例,讲解一下对应的相关操作:

其对应的汇编代码如下所示:

需要注意的是,在调用main函数之前,调用main函数的那两个函数的栈帧已经被创建好了!

这里我们对上面出现的汇编指令做一些简单的解释:

  • push实际上就是压栈,将对应的数据压入栈中;
  • mov:实际是就是赋值,这里mov ebp esp实际上就是把esp赋值给ebp;
  • sub:减去对应的地址;
  • lea(load effecitive address):计算内存地址并存入到寄存器当中(不访问内存,仅计算结果);

这里实际上lea到rep stos这四行汇编代码的作用就是将对应的栈的空间的数据都初始化为cc!

  • 压栈:在栈顶上放一个元素;
  • 出栈:从栈顶删除一个元素;

 截止到现在,做的都是初始化相关的任务,此时才开始到函数体内执行对应的任务;

假设每一行代表4个字节:

表示的就是将10这个值放到ebp-8的位置处;

         可以看到,也就是在ebp-8的位置上放10!

        需要注意的是,上面这里我们是把10放进入了,如果没有把10放进入,此时就是默认提供的随机值,因此在C语言中,如果我们没有进行初始化经常会打印出一堆烫烫烫烫(此时就是对应的内存栈上放的是一堆cccc)

此时可以看到对应的内存对其进行了修改(小端存储) 

接下来我们再看int b = 20;这条汇编代码:

dword ptr [ebp-14h], 14h

这段代码实际上就是在ebp-14h这个地址处,填充数字20;

对应的示意图如下所示:

 接下来我们再把int c = 0;也是在对应的栈上进行初始值:

当我们定义好对应的变量时,此时我们会调用add函数:

接下来我们按照对应的汇编代码进行分析:

  • 这里eax指向[ebp -14h],也就是让eax指向b;
  • 然后对eax进行压栈;
  • 接下来让ecx的值指向[ebp-8],也就是ecx指向a;
  • 然后在对ecx进行压栈;

即截止到现在,我们进行的任务就是我们对应的传参工作!

 接下来这里我们要调用对应的call指令:

call指令此时会跳转地址,即这里会跳转到我们对应的红色线框对应的地址!

这里需要注意的是,call完成了两个任务:

  • 将下一条指令的地址(00C21450)进行压栈,压入到栈中;
  • 跳转到对应的地址执行函数体;

接下来就跳转到对应的函数体当中:

其中,上面一堆的逻辑和main函数一样,都是开辟对应的空间,然后进行初始化;

实际上代码逻辑和我们上面讲的是一样的;

此时,我们依然假设每一行是4个字节,即此时每一行可以代表一个整形:

接下来我们依次看对应的汇编代码:

int z = 0;
mov dword ptr [ebp-8,0]

 这里实际上就是把ebp-8指向的这个空间初始化为0;

然后这里把[ebp+8]的值赋值给eax当中:

这里[ebp+8]的值实际上就是之前我们的ecx的值也就是10!

然后再加上[ebp+och]的值,och换算为10进制为12,也就是这里我们之前eax的值!

加完之后,再把算出来的结果返回到ebp-8当中,也就是z!

问题:我们在函数栈帧中有创建对应的形参吗?

没有!在我们call进入函数体之前,我们就通过形参压栈到对应的栈帧当中!

并且参数的压栈顺序是从右向左!

问题:如何再理解形参是实参的一份临时拷贝呢?

这里我们在梳理下逻辑:

  • 在调用add函数之前,会对形参从右到左进行一份拷贝;
  • 而形参是调用函数之前,从main函数里面拷贝的实参!

可以看到上面的ecx和eax是对应的a和b的一份临时拷贝!

所以改变形参不会改变对应的实参!

接下来我们再回到上面对应的代码当中:上面我们只是把计算的值写入到了z当中;

这里我们重点看return z的汇编代码:

mov eax,dword ptr[ebp-8]

这里是把对应的返回值存入到了eax寄存器当中;

需要注意的是寄存器的值不会随着函数栈帧被销毁而丢失!

接下来执行pop对应的汇编代码,这里也就是出栈,对应的esp栈顶指针会进行移动:

由于此时结果已经运行出来,保存到了eax寄存器当中,所以这里接下来直接对栈帧销毁即可!

这里直接进行:

mov ebp,esp

此时栈顶指针直接指向栈底指针!

然后让栈底指针进行出栈:

pop ebp

 实际上就是将add函数的栈底指针出栈,恢复到main函数当中;

pop:不仅对应的空间进行出栈,此时esp还需要+4个字节的地址;

ret

 ret指令实际上就是从栈顶跳出之前call的下一条指令的地址,然后跳转过去;

需要注意的是:ebp和esp维护的是当前执行的函数的栈帧空间,而不是整个程序的栈帧空间!

接下来就返回到执行call之后的部分:

这里执行对esp执行add,实际上就是将对应的压栈的形参进行销毁;

此时上面压栈的两个形参也会被销毁掉; 

然后将eax的值赋值给[ebp - 20h]这个位置!

那么这个ebp-20是什么呢?

实际上就是我们对应的参数c的值,这里把计算返回的值交到c当中!

讲到这里,我们就实现了从计算值然后从函数栈帧返回出来的处理;

main函数的函数栈帧和add的大同小异,所以这里我们就不再过多介绍了!

所以接下来我们就可以回答一些问题了:

问题:为什么局部变量不初始化的时候是随机值呢?

因为这里的局部变量是我们按要求设置的,例如vsstudio都初始化为cc;

问题:函数是如何进行传参的?

实际上当我们还没有调用函数的时候,此时形参就进行从右到左依次进行压栈处理(临时拷贝一份);

问题:函数调用的结果怎么返回?

值保存到寄存器当中,例如eax当中;且call会对下一条指令的地址进行压栈,运行结束后再取出地址;

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

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

相关文章

CPU-GPU-NPU-TPU 概念

1.CPU 中央处理器(Central Processing Unit,简称CPU)作为计算机系统的运算和控制核心,是信息处理、程序运行的最终执行单元。CPU自产生以来,在逻辑结构、运行效率以及功能外延上取得了巨大发展。 2.GPU GPU&#xff0…

Java学习手册:ORM 框架性能优化

一、优化实体类设计 减少实体类属性 :仅保留必要的字段,避免持久化过多数据。例如,对于一个用户实体类,如果某些信息(如详细地址)不是经常使用,可以将其拆分到单独的实体类中。使用合适的数据类…

XMP-Toolkit-SDK 编译与示例程序

一、前言 最近在调研图片的元数据读写方案,需要了解 XMP 空间以及如何在 XMP 空间中读写元数据,本文做一个相关内容的记录。 XMP-Toolkit-SDK 以及 XMP标准简介 XMP-Toolkit-SDK 是 Adobe 提供的一套开源软件开发工具包(SDK)&a…

计算机硬件(南桥):主板芯片组FCH和PCH的区别

在计算机主板设计中,FCH(Fusion Controller Hub)和PCH(Platform Controller Hub)分别是AMD和Intel对主板芯片组中“南桥”(Southbridge)部分的命名。尽管两者功能相似,但受不同厂商架…

数据库系统概论-基础理论

数据库系统概述: 1、记录:计算机中表示和存储数据的一种格式或方法。 2、数据库(DataBase, DB):数据库是长期储存在计算机内、有组织、可共享的大量数据集合。可为各种用户共享。 3、数据库管理系统(Dat…

在 R 中,清除包含 NA(缺失值)的数据

在 R 中,清除包含 NA(缺失值)的数据可以通过多种方式实现,具体取决于你希望如何处理这些缺失值。以下是几种常见的方法,包括删除包含 NA 的行、删除包含 NA 的列,或者用特定值填充 NA。 1. 删除包含 NA 的…

晶体布局布线

1Clock时钟电路 时钟电路就是类似像时钟一样准确运动的震荡电路,任何工作都是依照时间顺序,那么产生这个时间的电路就是时钟电路,时钟电路一般是由晶体振荡器、晶振、控制芯片以及匹配电容组成 2.时钟电路布局 晶体电路布局需要优先考虑&…

机器学习之嵌入(Embeddings):从理论到实践

机器学习之嵌入(Embeddings):从理论到实践 摘要 本文深入探讨了机器学习中嵌入(Embeddings)的概念和应用。通过具体的实例和可视化展示,我们将了解嵌入如何将高维数据转换为低维表示,以及这种转换在推荐系统、自然语言处理等领域的实际应用…

Python初学者笔记第九期 -- (列表相关操作及列表编程练习题)

第17节课 列表相关操作 无论是内置函数、对象函数,用起来确实很方便,但是作为初学者,你必须懂得它们背后的运行逻辑! 1 常规操作 (1)遍历 arr [1,2,3,4] # 以索引遍历:可以在遍历期间修改元素 for ind…

云计算与大数据进阶 | 25、可扩展系统构建

在进入这个进阶版系列之前,让我们先回顾一下云计算与大数据系统的基本设计原则,总结起来有如下几条: (1)基础架构:更多采用商品现货硬件(如PC架构)​,而很少使用定制化高端(如小型主…

C——函数递归

在 C 语言里&#xff0c;函数递归是一种函数调用自身的编程技术。下面开始逐一介绍。 一、什么是递归&#xff1f; 递归其实是⼀种解决问题的⽅法&#xff0c;在C语⾔中&#xff0c;递归就是函数⾃⼰调⽤⾃⼰。 写⼀个史上最简单的C语⾔递归代码&#xff1a; #include <st…

IdeaVim配置指南

一、什么是 IdeaVim&#xff1f; IdeaVim 是 JetBrains 系列 IDE&#xff08;如 IntelliJ IDEA, WebStorm, PyCharm 等&#xff09;中的一个插件&#xff0c;让你在 IDE 里使用 Vim 的按键习惯&#xff0c;大大提升效率。 安装方法&#xff1a; 在 IDE 中打开 设置(Settings) →…

Notepad++中XML格式化插件介绍

Notepad++中XML格式化插件介绍 背景安装指南安装步骤验证安装成功安装失败可尝试使用说明XML文件格式正确时格式化错误格式检查XML Tools插件核心功能盘点常见问题格式化后没变化中文显示乱码拯救杂乱XML格式!Notepad++这个神器插件,必须接收!背景 接手别人写的XML,缩进乱成…

自动化创业机器人:现状、挑战与Y Combinator的启示

自动化创业机器人&#xff1a;现状、挑战与Y Combinator的启示 前言 AI驱动的自动化创业机器人&#xff0c;正逐步从科幻走向现实。我们设想的未来是&#xff1a;商业分析、PRD、系统设计、代码实现、测试、运营&#xff0c;全部可以在monorepo中由AI和人类Co-founder协作完成…

第1章 算法设计基础

1-1 什么是算法 见识算法 算法是计算机科学的基石&#xff1a;从古代算术到现代计算机&#xff0c;算法始终是解决问题的核心。 算法的起源 张苍《九章算术》&#xff1a;创立了机械化算法体系&#xff08;如“合分术”分数相加算法&#xff09;。 欧几里得《几何原本》&am…

java中ArrayList扩容机制的解析

本文将系统地介绍 Java 中 ArrayList 的扩容机制&#xff0c;包括其初始容量的设置、触发扩容的时机、容量增长算法、扩容的详细流程以及性能优化建议&#xff0c;帮助读者从源码层面深入理解这一关键特性&#xff0c;并在实际开发中合理预分配容量以提升性能。 一、ArrayList…

【网络服务器】——回声服务器(echo)

作用 实现回声服务器的客户端/服务器程序&#xff0c;客户端通过网络连接到服务器&#xff0c;并发送任意一串英文信息&#xff0c;服务器端接收信息后&#xff0c;执行数据处理函数&#xff1a;将每个字符转换为大写并回送给客户端显示。 客户端&#xff1a;发送字符信息 服…

智能学习空间的范式革新:基于AI驱动的自习室系统架构与应用研究

摘要 在 “互联网 + 教育” 深度融合的背景下,传统自习室面临个性化服务缺失、学习效率低下等瓶颈。本文提出一种基于人工智能技术的 AI 自习室系统架构,通过构建多模态数据感知、个性化学习引擎及智能环境调控模块,实现学习过程的精准化、智能化与沉浸式体验。研究结合计算…

HTML01:HTML基本结构

HTML基本结构 <html> <head><meta charset"UTF-8"><title>我的第一个网页</title> </head> <body>我的第一个网页 </body> </html><body、</body等成对的标签&#xff0c;分别叫开发标签和闭合标签单独…

Spring Boot 实现多种来源的 Zip 多层目录打包下载(本地文件HTTP混合)

需要将一批文件&#xff08;可能分布在不同目录、不同来源&#xff09;打包成Zip格式&#xff0c;按目录结构导出给用户下载。 1. 核心思路 支持将本地服务器上的文件&#xff08;如/data/upload/xxx.jpg&#xff09;打包进Zip&#xff0c;保持原有目录结构。支持通过HTTP下载…