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

1.什么是函数栈帧

函数栈帧(stack frame)就是函数调用过程中在程序的调用栈(call stack)所开辟的空间,这些空间是用来存放:


函数参数和函数返回值


临时变量(包括函数的非静态的局部变量以及编译器自动生产的其他临时变量)


保存上下文信息(包括在函数调用前后需要保持不变的寄存器)。

我们可能常常对这些问题感到困惑:

函数是如何创建的呢?

函数又是如何调用的呢?

为什么在创建好一个变量不初始化就是随机值呢?

函数是如何将值反回来的?

参数又是如何给函数传递的?顺序又是怎样的呢? 

经过这篇文章相信能让你开启一个新世界的大门;

2.函数栈帧的创建与销毁解析

在开始前先让我们认识一下我们会遇到的一些汇编指令:

寄存器:

eax:通用寄存器,保留临时数据,常用于返回值
ebx:通用寄存器,保留临时数据
ebp:栈底寄存器
esp:栈顶寄存器
eip:指令寄存器,保存当前指令的下一条指令的地址

汇编指令:

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

我使用的是vs2022的编译器进行的操作;

首先我们要明确一个东西,在栈上的空间是由高到低使用的;

1.栈顶指针和栈底指针

我们给函数开辟一块栈区空间,那么就是将这块空间划分给了这块函数,那么就得限制这个空间的边界,不能让别的函数在这个空间进行操作,也不能让这个函数跑到别的空间中;

当调用哪个函数的时候,esp和ebp就会跑去维护哪个函数的函数栈帧空间;

这个时候我们就可以用2个指针来维护这块区域;ebp和esp是存放的是地址;

esp用来存放栈顶的地址,ebp用来存放栈底的地址;esp~ebp之间的空间就是操作系统分配给这次函数调用所开辟的空间;

2.函数的调用关系

我们可以在编译器中查看函数的互相调用关系;


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

以这段代码为例:

通过这段代码我们发现main函数也是被调用的;main函数被invoke_main()调用,这个函数又被

_scrt_common_main_seh()调用;往后依次这样的顺序;

3.函数栈帧的创建

前面我们知道了main函数也是被调用的,那么当我们执行到main函数的时候肯定也在前面创建了一个函数栈帧来调用main函数;

前面的invoke main函数肯定也有一块属于自己的函数栈帧空间;创建好了之后就开始调用main函数了,让我们具体来看看main函数是如何创建自己的函数栈帧空间的;

下面给大家上内存观察:

 push压入一个元素之后,esp的地址也变小了;

mov将esp的值给ebp,ebp指向了一个新的地址;

sub指令,esp-0E4h,0E4h(228)是一个16进制数字,地址减去数字还是一个地址,此时esp指向了一个新的地址;此时的esp-ebp的函数栈帧空间范围就已经创建好了;

执行3次push,esp的地址变小;esp指向了一块新的空间;

将edi这个地址开始向下进行9次将4个字节的空间初始化为0cccccccc;

到这个时候,main函数的函数栈的创建才正式完成;

4.局部变量的创建

内存观察:

局部变量也就全部创建好了,从这里我们也可以发现,为什么我们不初始化的话为什么里面会是随机值,这个随机值其实是在创建函数栈帧的时候自己放进去的;

5.函数调用的解析

在函数调用前我们先进行传参数;

mov 将ebp-14h这个地址向后4个字节的数据交给eax;然后将eax压栈;

mov 将ebp-8这个地址向后4个字节的数据交给ecx;然后将ecx压栈;

这一步就是传参;

接下来这一条指令非常重要,call指令将它下面一条指令的地址压入栈中保存;

jmp开始跳转到Add函数;

然后又是我们熟悉的开辟栈帧空间,创建局部变量;

我们好像貌似并没有发现Add的函数栈帧空间里面有x和y啊,那么形参在哪去了呢?我们接着往下看;

将ebp+8这个地址向后的4个字节的数据给eax;

ebp-8不就是我们之前往栈区压入的元素吗?

执行add指令,然后将ebp+0ch地址向后4个字节的元素相加到eax;

再将eax放入到ebp-8向后的4个字节的空间里;

然后开始把值往回带,我们注意最后一条mov指令,将ebp-8往后4个字节的空间的数据放入寄存器eax中;因为执行完条语句我们就会将函数栈帧销毁,所以这条代码的实际上的意义是将我们的答案存入到寄存器当中,并不是直接返回答案;

执行完后,就要回收空间还给操作系统了;

先弹出栈顶的三个元素然后将值赋给自己,然后esp+0cch,esp指向了栈底;mov将ebp的地址给esp,此时空间已经回收完毕;

弹出ebp(我们在栈上压入了一个元素,这个元素的内容是main函数的栈底地址)将ebp对值给自己,然后ebp回到了main函数的栈底的地址;

执行ret指令,恢复返回地址,别忘了我们在执行call指令的时候还将call指令单下一条指令单地址保存了起来,这个时候就放回保存的那个地址,至此又回到了main函数里面;但是此刻返回值还没有带回来;

先给esp加8,让栈顶指针回到main函数的栈顶;

mov将eax的值交给ebp-20h,那这不就是我们的c吗?至此函数的返回值也带回来了;

函数栈帧是如何销毁,形参是实参的一份临时拷贝,传参的顺序这些通过这一篇文章都能够知道

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

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

相关文章

【OCR】 - Tesseract OCR在mac系统中安装

Tesseract OCR 在Mac环境下安装Tesseract OCR(Optical Character Recognition)通常可以通过Homebrew包管理器进行。以下是安装步骤: 安装Homebrew 如果你还没有安装Homebrew,请访问 https://brew.sh/ 并按照页面上的说明安装。…

继续理解Nacos的CP和AP架构模型!

本篇文章延续文章“如何理解Nacos册CP和AP架构模型”,大家可以配套一起学习。 Nacos注册中心处理HTTP注册请求 在文章“如何理解Nacos册CP和AP架构模型”中已经提到过,Nacos注册中心用Restful API InstanceController的方法register()处理HTTP类型的注…

使用Docker-compose快速构建Nacos服务

在微服务架构中,服务的注册与发现扮演着至关重要的角色。Nacos(Naming and Configuration Service)是阿里巴巴开源的服务注册与发现组件,致力于支持动态配置管理和服务发现。最近,一位朋友表达了对搭建一套Nacos开发环…

算法训练营Day36(贪心-重叠区间)

都算是 重叠区间 问题,大家可以好好感受一下。 都属于那种看起来好复杂,但一看贪心解法,惊呼:这么巧妙! 还是属于那种,做过了也就会了,没做过就很难想出来。 不过大家把如下三题做了之后&#…

个性化Python GUI计算器搭建

大家好,本文将介绍在Python中使用Tkinter几分钟内制作自己的全功能GUI计算器。 要完成所提到的功能,除了通常随Python标准库一起安装的Tkinter之外,不需要任何额外的库。 如果使用的是Linux系统,可能需要安装: $ pi…

Spring MVC组件

1.DispatcherServlet前端控制器 用户请求到达前端控制器,它就相当于mvc模式中的c,dispatcherServlet 是整个流程控制的中心,由它调用其它组件处理用户的请求,dispatcherServlet 的存在降低了组件之间的耦合性。 2.HandlerMappin…

【HarmonyOS】掌握 Stage 模型的核心概念与应用

从今天开始,博主将开设一门新的专栏用来讲解市面上比较热门的技术 “鸿蒙开发”,对于刚接触这项技术的小伙伴在学习鸿蒙开发之前,有必要先了解一下鸿蒙,从你的角度来讲,你认为什么是鸿蒙呢?它出现的意义又是…

【Docker基础一】Docker安装Elasticsearch,Kibana,IK分词器

安装elasticsearch 下载镜像 查看版本:Elasticsearch Guide [8.11] | Elastic # 下载镜像 docker pull elasticsearch:7.17.16 # 查看镜像是否下载成功 docker images创建网络 因为需要部署kibana容器,要让es和kibana容器互联 # 创建一个网络&…

第28关 k8s监控实战之Prometheus(五)

------> 课程视频同步分享在今日头条和B站 大家好,我是博哥爱运维。这节课我们利用prometheus来监控入口流量控制服务nginx ingress controller。 我们前面部署过ingress-nginx,这个是整个K8s上所有服务的流量入口组件很关键,因此把它的…

在生产环境中使用uWSGI来运行Flask应用

安装uwsgi pip install uwsgi -i https://pypi.tuna.tsinghua.edu.cn/simple安装不上则使用以下命令: conda install -c conda-forge uwsgi 当您成功安装uwsgi后,您可以通过以下步骤来测试uwsgi是否安装成功: 创建一个Python脚本&#xff…

强化学习在生成式预训练语言模型中的研究现状简单调研

1. 绪论 本文旨在深入探讨强化学习在生成式预训练语言模型中的应用,特别是在对齐优化、提示词优化和经验记忆增强提示词等方面的具体实践。通过对现有研究的综述,我们将揭示强化学习在提高生成式语言模型性能和人类对话交互的关键作用。虽然这些应用展示…

《A++ 敏捷开发》- 3 克服拖延症

技术总监问:现在我遇到最大的难题就是如何提升下面技术人员的能力,如果他们全都是高手,我就很轻松了,但实际上高手最多只有 1/3,其他都是中低水平。你接触过这么多软件开发团队,有什么好方案? 我…

美团点评秋招前端测评分享

一. 选择题 1. 甲乙二人各自加工一批同样数量的零件,甲完成一半时,乙完成150个,甲全部完成时,乙完成全部的5/6,求这批零件一共有(C)个 A. 320 B. 400 C. 360 D. 420 2. 分析如…

用PreMaint引领先进的预测性维护

在设备维护领域,预测性维护成为一项利用先进技术和巧妙工具的数据驱动战略。这一战略通过条件监控和数据分析,以主动维护的方式识别潜在的设备缺陷,避免问题升级。高效使用PreMaint预测性维护工具可不仅节省时间和成本,更显著提升…

Redis的实现一:c、c++的网络通信编程技术,先实现server和client的通信

由于,本人是主修java的,所以以下内容可能不是很精通,各位看完后尽可评论。 以下皆是在linux的描述 第一步,通过socket拿到fd Socket()函数:创建用于通信的端点并返回描述符。 int fd socket(AF_INET, SOCK_STREAM…

Java药物不良反应ADR智能监测系统源码

药物不良反应(Adverse Drug Reaction,ADR)是指在使用合格药品时,在正常的用法和用量下出现的与用药目的无关的有害反应。这些反应往往因药物种类、使用方式、个体差异等因素而异,可能导致患者身体不适、病情恶化。 为保…

什么事“网络水军”?他们的违法活动主要有四种形式

我国治理网络水军,包括造谣引流、舆情敲诈、刷量控评、有偿删帖等各类“网络水军”等违法犯罪活动已经许久。 日前,官方召开新闻发布会,公布了相关的一些案件进程,今年已累计侦办相关案件339起,超过历年的全年侦办案件…

创建ESP32开源WiFi MAC(介质访问控制)层

内置WiFi 内置的 WiFi.h 库将使我们能够轻松使用 ESP32 板的 WiFi 功能。 连接到 Wi-Fi 接入点&#xff1a; #include <WiFi.h>const char* ssid "yourNetworkName"; const char* password "yourNetworkPassword";void setup(){Serial.begin(11…

GraalVM 原生镜像支持中文文档

本文为官方文档直译版本。原文链接 GraalVM 原生镜像支持中文文档 引言GraalVM 原生镜像介绍与 JVM 部署的主要区别了解 Spring Ahead-of-Time 处理源代码生成生成提示文件生成代理类 开发您的第一个 GraalVM 原生应用程序应用样本使用构建包构建原生映像系统要求使用 Maven使用…