C语言函数栈帧的创建和销毁(逐步分析)

什么是函数栈帧

我们在写C语言代码的时候,经常会把一个独立的功能抽象为函数,所以C程序是以函数为基本单位的。 那函数是如何调用的?函数的返回值又是如何返回的?函数参数是如何传递的?这些问题都和函数栈帧有关系。

函数栈帧(stack frame)就是函数调用过程中程序的调用栈(call stack)所开辟的空间,这些空间是用来存放: 1. 函数参数和函数返回值 2. 临时变量(包括函数的非静态的局部变量以及编译器自动生产的其他临时变量) 3. 保存上下文信息(包括在函数调用前后需要保持不变的寄存器)。

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

每一个函数调用都要在栈区穿件一个空间


寄存器

寄存器是 CPU 内部用来存放数据的一些小型 存储区域 ,用来暂时存放参与运算的数据和运算结果。 其实寄存器就是一种常用的 时序逻辑电路 ,但这种时序逻辑电路只包含存储电路。

有:eax   ebx    ecx     edx     ebp     esp

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


举例说明

我们可以深入探究下函数调用在内存空间中到底是怎么运转的

我们可以以下面代码为例来分析

#define _CRT_SECURE_NO_WARNINGS 1
#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;
}

首先我们先建立main函数的栈帧空间,但我们思考一下,main函数是不是也有可能被其他函数调用那。

我们可以看出在vs2013中mainCRTStartup调用_tmainCRTStartup,而_tmainCRTStartup调用main,可见main函数也是被调用的。

首先,创建一个_tmainCRTStartup函数栈帧,我们假设栈区下面为高地址,上面为地地址。


esp为栈顶指针,ebp为栈底指针


这样我们就可以进入,我们通过汇编代码可以看出第一步为push。

push为压栈操作,push的目标是ebp,所以压栈ebp,压完元素之后,esp移动到新的栈顶。压栈:给栈顶放一个元素进去 。出栈:从栈顶删除一个元素。


下一步是move,将ebp移动到esp

 


下一步是sub,sub是减的意思,意思是将esp减去0E4h(16进制数字),相当于往低地址方向移动

0E4h地址,此刻esp与ebp围成的紫色部分就是main函数的栈帧


然后是三个push分别将ebx esi edi从栈顶压入,最终esp移动到edi的上方


从lea到rep,这几步总的来说是将main函数栈帧里面都初始化“ccccccccccc” 


 以上就是main函数栈帧创建,接下来就是把值放进去,int a=10,dword是双字节的意思,将a的值放在ebp-8这个空间里


接下来就把b, c也像a一样分别放入对应的位置 


接下来就是传参,将ebp-14h也就是b的空间放入eax寄存器里面,再push一下放入栈顶

 


再传a,a也一样的道理


接下来就是call指令,call存放的是下一个指令的地址,方便函数返回时直接跳到下一指令

 


这下算是进入Add函数了创建Add函数栈帧与那main一样 先push ebp将main函数栈底指针地址通过这个元素储存起来方便返回时能找到main函数栈底指针


再move,sub将esp和ebp定义新的位置,再push三个元素ebx,esi,edi,最后再将Add函数栈帧初始化“CCCCCCCCC”


接着给z创建空间,ebp-8的位置


然后就是将传过去的b和a加起来储存在eax寄存器里面

 


接着将eax里面的值移动到z空间里(ebp-8),此时z空间的值是30,再将这个值放入eax寄存器中,这一步防止函数栈帧销毁时数据流失,所以将值保存在eax中


调用完就开始返回了,pop意思是跳出 ,把这三个元素先跳出


再将esp返回到ebp的位置


此刻esp指向的是我们先前放进的ebp在main函数底栈时的地址,把当时ebp在main函数底栈位置读取用pop,ebp又指向回了main函数的栈底,而esp继续停留这个位置


接着是ret指令,意思是返回到main函数,返回到call指令,而call指令储存的是下一个指令的地址,所以直接返回main函数call指令下一个指令也就是a传参的空间。此刻esp指向的就是a传参的空间。

 


 然后esp+8,跳出俩传参的空间


再三个pop,将edi,esi,ebx跳出去,到达main函数的空间。


最后将承载着z的值也就是两数和的值的寄存器eax,将值付给ebp-20h也就是c的地址

 此时c就为30了


 结论

局部变量是怎么创建的

创建好函数栈帧后,我们初始化一部分函数空间,而局部变量就在这个空间里分配一个空间,从而创建了局部变量

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

因为随机值是在我们创建函数栈帧时放进去的,函数空间里都是随机值,所以一定要初始化。

函数是怎么传参的,函数传参的顺序是什么

我们通过push将两个实参压栈,从而栈顶有了两个独立空间,将两个值放进去,创建好调用的函数栈帧后,通过指针的偏移量,实现传参。传参顺序从从右向左

形参和实参是什么关系

形参是实参的临时拷贝

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

我们通过push将当时edp在主函数栈底的地址压栈到一个空间,当我们返回指向这个空间是就能读取到主函数栈底的位置,再读取通过call指令存放下一个指令的地址,就直接返回主函数的栈帧里,返回值是通过寄存器存储,保护数据在调用的函数栈帧销毁时不丢失,再通过寄存器将值放入对应的主函数空间

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

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

相关文章

数据结构(C语言)代码实现(八)——顺序栈实现数值转换行编辑程序汉诺塔

目录 参考资料 顺序栈的实现 头文件SqStack.h&#xff08;顺序栈函数声明&#xff09; 源文件SqStack.cpp&#xff08;顺序栈函数实现&#xff09; 顺序栈的三个应用 数值转换 行编辑程序 顺序栈的实现测试 栈与递归的实现&#xff08;以汉诺塔为例&#xff09; 参考资…

Stable Diffusion 模型下载:Samaritan 3d Cartoon(撒玛利亚人 3d 卡通)

文章目录 模型介绍生成案例案例一案例二案例三案例四案例五案例六案例七案例八案例九案例十 下载地址 模型介绍 由“PromptSharingSamaritan”创作的撒玛利亚人 3d 卡通类型的大模型&#xff0c;该模型的基础模型为 SD 1.5。 条目内容类型大模型基础模型SD 1.5来源CIVITAI作者…

编译原理实验1——词法分析(python实现)

文章目录 实验目的实现定义单词对应的种别码定义输出形式&#xff1a;三元式python代码实现运行结果检错处理 总结 实验目的 输入一个C语言代码串&#xff0c;输出单词流&#xff0c;识别对象包含关键字、标识符、整型浮点型字符串型常数、科学计数法、操作符和标点、注释等等。…

飞天使-linux操作的一些技巧与知识点8-zabbix6.0 容器搭建

文章目录 安装docker安装步骤mysql下载镜像安装zabbix 测试效果 安装docker 1. 配置官方 yum 源$ sudo yum install -y yum-utils $ sudo yum-config-manager \--add-repo \https://download.docker.com/linux/centos/docker-ce.repo2. 安装 Docker$ sudo yum install docker-c…

Docker的镜像和容器的区别

1 Docker镜像 假设Linux内核是第0层&#xff0c;那么无论怎么运行Docker&#xff0c;它都是运行于内核层之上的。这个Docker镜像&#xff0c;是一个只读的镜像&#xff0c;位于第1层&#xff0c;它不能被修改或不能保存状态。 一个Docker镜像可以构建于另一个Docker镜像之上&…

go语言进阶篇——面向对象(一)

什么是面向对象 在我们设计代码时&#xff0c;比如写一个算法题或者写一个问题结局办法时&#xff0c;我们常常会使用面向过程的方式来书写代码&#xff0c;面向过程主要指的是以解决问题为中心&#xff0c;按照一步步具体的步骤来编写代码或者调用函数&#xff0c;他在问题规…

Linux系统安全之iptables防火墙

目录 一、iptables防火墙的基本介绍 1、netfile与iptables的关系 1.1netfile 1.2iptables 1.3iptables是基于内核的防火墙&#xff0c;其中内置了raw&#xff0c;mangle&#xff0c;nat和filter四个规则表 2、iptables防火墙默认规则表&#xff0c;链结构 二、iptables的…

Qt程序设计-读写CSV文件

本文实例演示Qt读写CSV文件实现 创建项目 添加两个按钮和一个显示路径的label 界面如下 UI界面 <?xml version="1.0" encoding="UTF-8"?> <ui version="4.0"><class>MainWindow</class><widget class="QM…

文档协作技术——Operational Transformations简单了解

OT是支持协作软件系统的一种广泛使用的技术。 OT通常使用副本文档储存&#xff0c;每个客户端都拥有对文档的副本。客户端在本地副本以无锁非堵塞方式操作&#xff0c;并将改变传递到其他客户端。当客户端收到其他客户端传播的改变之后&#xff0c;通过转换应用更改&#xff0…

视频上传 - 断点续传那点事

在上一篇文章中&#xff0c;我们讲解了分片上传的实现方式。在讲解断点续传之前&#xff0c;我要把上篇文章中留下的问题讲解一下。读过上一篇文章的小伙伴们都知道&#xff0c;对于分片上传来说&#xff0c;它的传输方式分为2种&#xff0c;一种是按顺序传输&#xff0c;一种是…

#免费 苹果M系芯片Macbook电脑MacOS使用Bash脚本写入(读写)NTFS硬盘教程

Mac电脑苹果芯片读写NTFS硬盘bash脚本 &#xff08;ntfs.sh脚本内容在本文最后面&#xff09; ntfs.sh脚本可以将Mac系统(苹果M系芯片)上的NTFS硬盘改成可读写的挂载方式&#xff0c;从而可以直接往NTFS硬盘写入数据。此脚本免费&#xff0c;使用过程中无需下载任何收费软件。…

Cocos creator 3.x 刚体组件碰撞无效

Cocos creator 3.x 刚体组件碰撞无效 问题描述&#xff1a;只有一个circleCollider2D时&#xff0c;可以在碰撞时正确输出结果&#xff0c;但是当我在外围加了一个circle之后&#xff0c;期望character进入圆圈范围时就触发方法&#xff0c;此时原代码失效 import { _decorat…