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

news2024/11/16 2:27:55

大家好,我是深鱼~

【前言】前期学习的时候,我们可能有很多的困惑

比如:

·局部变量是怎么创建的呢?

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

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

·形参和实参是什么关系?

·函数调用是怎么做的?

·函数调用是结束后怎么返回的?

知道了函数栈帧的创建和销毁这些问题就都可以解决了,学习这个知识其实就是修炼自己的内功,也能搞懂后期的更多知识

 【进入正题】:

本部分讲解使用的环境是vs2013,因为越高级的编译器,越不容易学习和观察。

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

一、寄存器

电脑中的任何指令都是在CPU上运行的,但是CPU本身只负责运算不负责存储,数据一般都存储在内存和寄存器(存储最常见的数据

寄存器有eax,ebx,ecx,edx,还有本篇的重点ebp,esp

ebp(栈底指针),esp(栈顶指针)这2个寄存器中存放的是地址,这两个地址是用来维护函数栈帧的

二、栈区

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

那么什么是栈区呢?

内存四区模型:

栈区stack:由编译器自动分配和释放,存放函数的参数值(形参和返回值),局部变量的值

栈区的使用习惯是先使用高地址,再使用低地址,空间不断向上消耗,如果再使用空间,就需要在顶部放数据(栈顶),当函数调用时,esp维护的就是栈顶ebp维护的就是栈底

【温馨提示】:这里只是以main函数开辟的空间为例,esp,ebp不只是维护main函数,还有其他的函数

【问】:那么什么又是栈帧呢? 

 【栈帧】:利用寄存器访问局部变量,函数的参数值等的手段,表示程序的函数调用记录

【补充】:压栈和出栈是什么?

push:压栈:从栈顶上放入元素

pop:出栈:从栈顶上删除元素 

 三、函数栈帧的创建

代码:为了更清晰地演示此过程,所以代码写的足够的细节

#include<stdio.h>
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;
}

按下F10进行逐过程调试,按F11下一步,然后打开调试-窗口-调用堆栈,随着我们一步一步地按F11逐语句调试,最后发现从return 0跳转到另一个界面,其实就是main函数的调用函数

 其实main()函数是被_tmainCRTStartup函数调用,而_tmainCRTStartup函数又是被mainCRTStartup函数调用的

接下来分步骤演示函数栈帧的创建和销毁的过程

1.为main函数开辟栈帧 

<0>在调用main函数之前:esp和ebp分别在栈顶和栈底维护_tmainCRTStartup函数

<1>:将ebp的值压入栈中,esp的值向低地址减小,指向esp的上端

<2>:将esp的值赋给ebp,两者同时指向esp                         

<3>:将esp减去0E4h并保存在esp中,即esp向低地址移动0E4h个字节(就是开始为main函数开辟空间) 

<4>:将ebx,esi,edi的值分别压入栈中,注意每次压栈后,esp都会向低地址移动,最后sep就到达了edi的上方

<5>:将ebp减去0E4h的地址放到到edi中(lea:load effective address)

                      将39h放到ecx中去

                      将0CCCCCCCCh放到eax中去

<6>:将从edi(ebp-0E4h)开始的位置,重复39h次地向下(高地址方向)的内存赋值0CCCCCCCCh,每次赋值双字符(dword:double word)即四字节的空间(就是为main函数开辟的空间全部初始化为0CCCCCCCCh)

为main()函数开辟栈帧

                                                                                                                              

2.在main函数中创建变量

将初始化的值赋值给内存地址为【 】中的双字符空间

eg:int a=10:将0Ah(也就是初始化的值10)放到ebp-8为内存地址的空间

main3

 结果我们可以看到三个位置分别放入了变量并初始化了,从这里我们就可以知道为什么变量不初始化就是随机值了,因为初始化,变量里面就是0CCCCCCCCh,这个值是不确定的 

3.调用Add函数前的准备

<1>传参b:

(1)把地址为ebp-14h(b的地址)里面的数据(也就是b的值)赋给eax

(2)将eax的值(里面放的就是b的值20)压入栈中,esp向上(低地址)移动

<2>传参a:

(1)把地址为ebp-8(a的地址)里面的数据(也就是a的值)赋给ecx

(2)将ecx的值(里面放的就是a的值10)压入栈中,esp向上(低地址)移动

【注意】传参是从右往左传的

<3>准备调用Add函数:

(1)把call指令的下一条指令的地址压入栈中(这一步是为了调用完自定义函数后根据这个地址回到main函数去,并从这个地址向下执行),esp向上(低地址)移动

【注意】这个时候main函数的栈帧范围:栈顶esp已经到了这个地址,而栈底ebp还在下面

(2)程序进入到Add函数中去

main5

 4.为Add函数开辟栈帧

 这一步和为main函数开辟栈帧一样

<1>:将ebp的值压入栈中,esp向上(低地址)移动

<2>:将esp的值赋给ebp,两者同时指向esp   

<3>:将esp减去0CCh并保存在esp中,即esp向低地址移动0CCh个字节(就是开始为Add函数开辟空间) 

<4>:将ebx,esi,edi的值分别压入栈中,每次压栈后,esp都会向上(低地址)移动,最后sep就到达了edi的上方

<5>:将ebp减去0CCh的地址放到到edi中

                      将33h放到ecx中去

                      将0CCCCCCCCh放到eax中去

<6>:将从edi(ebp-0CCh)开始的位置,重复33h次地向下(高地址方向)的内存赋值0CCCCCCCCh,每次赋值双字符(四字节)的空间(就是为Add函数开辟的空间全部初始化为0CCCCCCCCh)

 5.在Add函数中创建变量并运算

<1>创建变量z:将0(z初始化的值)放到ebp-8为内存地址的空间

<2>进行运算:

(1)将地址为ebp+8里面的数据(也就是a)放入eax

(2)将地址为ebp+12里面的数据(也就是b)加到eax(上次的10+这次的20=30)

(3)将eax放入地址为ebp-8里面(也就是z,让z=30)

【注意】形参并不是在自定义函数里面创建的,而是回去找到调用函数之前压栈压进去值的地址

<3>返回z:将地址为ebp-8里面的数据(也就是30)放入eax(让eax读取z的值,防止函数销毁以后拿不回z的值)

Add2

四、函数栈帧的销毁

6.Add函数栈帧的销毁

<1>出栈:edi,esi,ebx分别弹出栈区,每一次出栈esp都向下(高地址)移动

<2>Add函数回收:将ebp的值赋给esp,add函数的栈就没了

<3>将栈顶main函数的ebp弹出,ebp直接回到了main函数的ebp,同时esp向下(高地址)移动4字节

<4>返回ret,程序自动返回刚才call指令的下一行,读完地址,esp又向下(高地址)移动4个字节

7.返回main函数栈帧

 <1>形参x,y空间销毁:esp+8即esp向下(高地址)移动8字节,将形参x,y空间也还给操作系统

<2>把计算结果给c:把eax(Add求和的结果)的值放到以ebp-20h(c的地址)为地址的空间中

 main函数的销毁和Add函数差不多,下面就不做过多的阐述

【总结】

以上就是函数栈帧的创建和销毁,可以看到这个过程非常的严谨,非常的奇妙

看到这里相信大家对前面提出的问题都已经有了答案,有些地方还是挺难理解的,有问题欢迎评论区或者私信交流,觉得笔者写的还可以,或者自己有些许收获的,麻烦铁汁们动动小手,给俺来个一键三连,万分感谢 

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

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

相关文章

上海亚商投顾:沪指放量大涨1.84% 证券股掀涨停潮

上海亚商投顾前言&#xff1a;无惧大盘涨跌&#xff0c;解密龙虎榜资金&#xff0c;跟踪一线游资和机构资金动向&#xff0c;识别短期热点和强势个股。 市场情绪 三大指数今日低开高走&#xff0c;沪指午后放量涨近2%&#xff0c;上证50盘中大涨超3%。大金融板块全线爆发&#…

Wireshark从下载到使用完整教程分享

surfshark如何下载呢&#xff1f;surfshark安卓&#xff0c;苹果以及电脑上使用的完整教程分享可以戳后面地址: https://qptool.net/shark.html Wireshark是一款流行的网络协议分析工具&#xff0c;用于捕获和分析网络数据包。它可以帮助网络管理员和安全专家监视和诊断网络问…

[每日习题]跳石板(动态规划) 手套(贪心)——牛客习题

hello,大家好&#xff0c;这里是bang___bang_&#xff0c;今天来记录2道习题跳石板和手套&#xff01; 目录 1️⃣跳石板 2️⃣手套 1️⃣跳石板 跳石板_牛客题霸_牛客网 (nowcoder.com) 描述 小易来到了一条石板路前&#xff0c;每块石板上从1挨着编号为&#xff1a;1、2、…

批量删除python代码中的注释

ctrlh&#xff0c;调出替换功能窗口 启用正则表达式&#xff0c;输入 (#.*) 点击替换就能删除全部的注释了

【图论】LCA(倍增)

一.LCA介绍 LCA通常指的是“最近共同祖先”&#xff08;Lowest Common Ancestor&#xff09;。LCA是一种用于解决树或图结构中两个节点的最低共同祖先的问题的算法。 在树结构中&#xff0c;LCA是指两个节点的最近层级的共同祖先节点。例如&#xff0c;考虑一棵树&#xff0c;…

16.Netty源码之ChannelPipeline

highlight: arduino-light 服务编排层:ChannelPipeline协调ChannelHandlerHandler EventLoop可以说是 Netty 的调度中心&#xff0c;负责监听多种事件类型&#xff1a;I/O 事件、信号事件、定时事件等&#xff0c;然而实际的业务处理逻辑则是由 ChannelPipeline 中所定义的 Cha…

《Elasticsearch 源码解析与优化实战》第5章:选主流程

《Elasticsearch 源码解析与优化实战》第5章&#xff1a;选主流程 - 墨天轮 一、简介 Discovery 模块负责发现集群中的节点&#xff0c;以及选择主节点。ES 支持多种不同 Discovery 类型选择&#xff0c;内置的实现称为Zen Discovery ,其他的包括公有云平台亚马逊的EC2、谷歌…

C++之普通函数指针/类成员函数指针/lambda回调函数总结(一百六十八)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 人生格言&#xff1a; 人生…

边写代码边学习之卷积神经网络CNN

1. 卷积神经网络CNN 卷积神经网络&#xff08;Convolutional Neural Network&#xff0c;CNN&#xff09;是一种深度学习神经网络的架构&#xff0c;主要用于图像识别、图像分类和计算机视觉等任务。它是由多层神经元组成的神经网络&#xff0c;其中包含卷积层、池化层和全连接…

算法与数据结构-二分查找

文章目录 什么是二分查找二分查找的时间复杂度二分查找的代码实现简单实现&#xff1a;不重复有序数组查找目标值变体实现&#xff1a;查找第一个值等于给定值的元素变体实现&#xff1a;查找最后一个值等于给定值的元素变体实现&#xff1a;查找最后一个小于给定值的元素变体实…

【雕爷学编程】MicroPython动手做(10)——零基础学MaixPy之神经网络KPU2

KPU的基础架构 让我们回顾下经典神经网络的基础运算操作&#xff1a; 卷积&#xff08;Convolution&#xff09;:1x1卷积&#xff0c;3x3卷积&#xff0c;5x5及更高的卷积 批归一化&#xff08;Batch Normalization&#xff09; 激活&#xff08;Activate&#xff09; 池化&…

玩一玩编程式 AOP

[toc] 平时我们项目中涉及到 AOP&#xff0c;基本上就是声明式配置一下就行了&#xff0c;无论是基于 XML 的配置还是基于 Java 代码的配置&#xff0c;都是简单配置即可使用。声明式配置有一个好处就是对源代码的侵入小甚至是零侵入。不过今天松哥要和小伙伴们聊一聊编程式的 …

Chapter 9: Lists | Python for Everybody 讲义笔记_En

文章目录 Python for Everybody课程简介ListsA list is a sequenceLists are mutableTraversing a listList operationsList slicesList methodsDeleting elementsLists and functionsLists and stringsParsing linesObjects and valuesAliasingList argumentsDebuggingGlossar…

【Spring】Spring 下载及其 jar 包

根据 【动力节点】最新Spring框架教程&#xff0c;全网首套Spring6教程&#xff0c;跟老杜从零学spring入门到高级 以及老杜的原版笔记 https://www.yuque.com/docs/share/866abad4-7106-45e7-afcd-245a733b073f?# 《Spring6》 进行整理&#xff0c; 文档密码&#xff1a;mg9b…

数字签名与数字证书

数字签名与数字证书 数字签名数字证书数字证书的原理数字证书的特点 如何验证证书机构的公钥不是伪造的 数字签名 数字签名是非对称密钥加密技术与数字摘要技术的应用&#xff0c;数字签名就是用加密算法加密报文文本的摘要&#xff08;摘要通过hash函数得到&#xff09;而生成…

「回溯框架」

文章目录 0 回溯和动态规划&#xff08;dp&#xff09;的区别0.1 框架 1 刷题1.1 全排列1.1.1 题解1.1.2 Code1.1.3 结果 1.2 N皇后1.2.1 题解1.2.2 Code1.2.3 结果 0 回溯和动态规划&#xff08;dp&#xff09;的区别 动态规划的核心是穷举&#xff0c;那么回溯算法和dp有什么…

单机最快的队列Disruptor解析和使用

前言 介绍高性能队列Disruptor原理以及使用例子。 Disruptor是什么? Disruptor是外汇和加密货币交易所运营商 LMAX group 建立高性能的金融交易所的结果。用于解决生产者、消费者及其数据存储的设计问题的高性能队列实现。可以对标JDK中的ArrayBlockingQueue。是目前单机且…

IDC报告背后:大模型时代,重新理解AI公有云

大模型之于AI公有云的意义&#xff0c;在于大模型可以改变过去“手工作坊定制算法”的高成本模式&#xff0c;转向“工厂模式”&#xff0c;只需要微调和精调&#xff0c;就可以形成针对性的场景算法。 作者|葛覃 出品|产业家 一年前&#xff0c;依然有不少云计算从业者思…

基于智能状态和源代码插桩的 C 程序内存安全性动态分析

原文来自微信公众号“编程语言Lab”&#xff1a;基于智能状态和源代码插桩的 C 程序内存安全性动态分析 搜索关注“编程语言Lab”公众号&#xff08;HW-PLLab&#xff09;获取更多技术内容&#xff01; 欢迎加入 编程语言社区 SIG-程序分析 参与交流讨论&#xff08;加入方式&a…

警惕!通过谷歌和必应搜索广告传播的新型恶意活动

据观察&#xff0c;一种新的恶意广告活动利用谷歌搜索和必应的广告&#xff0c;以AnyDesk、Cisco AnyConnect VPN和WinSCP等IT工具的用户为目标&#xff0c;诱骗他们下载木马安装程序&#xff0c;目的是入侵企业网络&#xff0c;并可能在未来实施勒索软件攻击。 Sophos在周三的…