图解简单C程序的运行时结构
程序在内存中的存储分为三个区域,分别是动态数据区、静态数据区和代码区。函数存储在代码区,全局变量以及静态变量存储在静态数据区,而在程序执行的时候才会在动态数据区产生数据。程序执行的本质就是代码区的指令不断执行,驱使动态数据区和静态数据区产生数据变化。
代码区与动态数据区由三个寄存器控制,分别是eip、ebp和esp。eip指向代码区下一个要执行的指令,ebp与esp分别指向动态数据区的栈底和栈顶。初始情况下eip默认指向main函数的第一条指令,esp、ebp指向的位置由程序加载时内核的设置决定。
我们看一下这段代码如何执行的,在执行第一条指令时,考虑到函数调用的问题,ebp会先把当前指向的地址记录到栈中,方便以后返回来继续执行。把地址压进栈时,esp就会自动往栈顶方向移动。说到这里,为避免混淆先科普一下什么是栈顶和栈底,栈只允许在一端做插入和删除操作,这一端就叫栈顶,而另一端叫做栈底,图中下方叫栈顶,上方叫栈底。esp永远在栈顶,也就是图的最下方。
由于esp指向的地址已经被记录,那么它就被空闲出来了。现在我们开始构建main函数的栈,空闲的esp帮忙看着main函数的栈底。这个时候esp与ebp是重叠的。
eip继续指向下一条指令,到了局部变量i的初始化,这里将i赋值为4,就将i的初始值压到栈中,esp继续往栈顶移动。下一条指令与本条相同,将局部变量j也压入栈中,如图所示。
接下来调用了fun函数,虽然fun函数是独立的函数,但是由于是在main函数中调用的,所以依然将数据压至main函数的栈中。fun函数的传入参数为i、j,但是入栈的顺序正好相反,b先入栈,然后a被压入栈中,如图所示。
接下来要跳转到fun函数了,在跳转之前,我们要先给fun函数的返回值留个位置,因为要赋值给局部变量m的。然后再将fun函数的返回地址压入栈中,方便执行完fun函数后能继续往下执行。最后再把ebp当前的地址值压入栈中,此时ebp指向的是main函数的栈底(如果这里不做保存,fun函数执行完ebp就回不去了)。
接下来就正式进入了fun函数,像第一次保存完地址值那样,ebp又被闲置了,所以让ebp守住fun函数的栈底。而局部变量b与c的赋值就不再多说,与main函数的执行过程相同,当走到了return时,将计算出的结果赋值写入到刚空出的返回值那里。
此时的fun函数就执行完毕了,我们要恢复main函数调用fun函数的现场,继续往下执行,要想往下执行,必须将ebp回到main函数的栈底,并且找到fun函数返回的位置,然后跳转到那里。很简单,由于刚才保存了ebp的地址值,所以将地址值赋值给ebp,ebp就指向了main函数的栈底。
ebp的地址值出栈后,esp就指向了fun函数的返回地址,通过执行ret指令,把该地址值传给eip,使eip指向fun函数执行后的返回地址。
这样就恢复了现场,然后把fun函数的返回值传递给m,此时局部变量b、a和返回值已经没有价值了,把它们清出栈,现在就剩下干干净净的栈内容了。
现在执行最后一步,main函数就结束了,此时局部变量i、j也没有任何作用,做清栈操作,清理出干净的栈空间。
以上便是一个简单C程序的运行时结构。本文总结于新设计团队的《编译系统透视:图解编译原理》,图侵删。
图解简单C程序的运行时结构的更多相关文章
- 更为复杂C程序的运行时结构
运行环境 win 10 企业版 1809 17763.194,MinGW V3.14 32位,Bundled V3.13.2,Bundled GDB V8.2. 在C语言中,栈的方向是从高地址向低地址 ...
- [快手(AAuto)学习笔记]如何让程序在运行时请求管理员权限(UAC)
作者:ffsystem 作为(糟糕的)程序猿,习惯写代码解决一些简单事务.正常用批处理就能解决大部分工作,复杂一点用AutoIt 3. 有时候要分发给别人,就需要一个界面.外行你程序写得如何他看不懂, ...
- java拾遗4----一个简单java程序的运行全过程
简单说来,一个java程序的运行需要编辑源码.编译生成class文件.加载class文件.解释或编译运行class中的字节码指令. 下面有一段简单的java源码,通过它来看一下java程序的运行流程: ...
- 如何让Qt程序在运行时获取UAC权限
在pro文件中加入以下语句: QMAKE_LFLAGS += /MANIFESTUAC:\"level=\'requireAdministrator\' uiAccess=\'false\' ...
- Laravel 使用 Provider 为程序提供运行时配置服务
需求: 配置参数存在数据库中,Model 是 aah,需要在每次运行时,程序可以在任何地方采用 config("aah.name") 的方式访问配置信息. 思路: 采用 Provi ...
- Java中获取类的运行时结构
获取运行时类的完整结构 通过反射获取运行时类的完整结构 Field(属性).Method(方法).Constructor(构造器).Superclass(父类).Interface(接口).Annot ...
- 关于java程序在运行时出现a java exception has occured时解决方法
错误截图: 出现情况原因分析: 1.环境没有配置好,配置java环境变量: 参考 检查是否正确,java javac,可以尝试重新 2.查看使用的jdk版本是否存在版本问题: 例如jdk1.7对中文的 ...
- c#+handle.exe实现升级程序在运行时自动解除文件被占用的问题
我公司最近升级程序经常报出更新失败问题,究其原因,原来是更新时,他们可能又打开了正在被更新的文件,导致更新文件时,文件被其它进程占用,无法正常更新而报错,为了解决这个问题,我花了一周时间查询多方资料及 ...
- 通过编写c语言程序,运行时实现打印另一个程序的源代码和行号
2017年6月1日程序编写说明: 1.实现行号的打印,实现代码的读取和输出,理解主函数中的参数含义. 2.对fgets函数理解不够 3.对return(1); return 0的含义理解不够 4.未实 ...
随机推荐
- 开发常用镜像资源替换为国内开源镜像(yum,compose,maven,docker,android sdk,npm,国内开源镜像汇总)
一.国内开源镜像站点汇总 阿里云开源镜像站 (http://mirrors.aliyun.com/)网易开源镜像站 (http://mirrors.163.com/)中国科学技术大学开源镜像站 (ht ...
- Excel:一维表和二维表 互转
一.一维表转二维表 数据源: 一份流水账式的值班表,为了便于打印张贴,现在需要使其变成这样的样式: 也就是从一维表变成传说中的二维表. 1.新建查询 依次单击[数据]→[新建查询] →[从文件]→[从 ...
- spring boot 分布式事务实现(XA方式)
关于spring boot 支持分布式事务,XA是常用的一种方式. 这里把相关的配置记下,方便以后使用. 首先配置两个不同的数据源 : 订单库.持仓库. /** * Created by zhangj ...
- Spark记录-org.apache.spark.sql.hive.HiveContext与org.apache.spark.sql.SQLContext包api分析
HiveContext/SQLContext val hiveContext=new HiveContext(new SparkContext(new SparkConf().setAppName(& ...
- nodejs读取json文件,写入mongodb数据库
最近又一点时间,开始使用mongodb存储json模型文件,然后可以实现模型文件的在线编辑和管理.今天上午实现了json文件入库的代码,如下: var fs=require("fs" ...
- ubuntu 下没有pthread库以及报undefined reference to 'pthread_create'的解决方法
https://blog.csdn.net/dyzhen/article/details/79058554
- JQuery 对表格的详细操作
<%@ page language="java" contentType="text/html; charset=UTF-8"pageEncoding=& ...
- iOS 中nil,Nil,NULL,NSNull的区别
类与对象的概念 类是对同一类事物高度的抽象,类中定义了这一类对象所应具有的静态属性(属性)和动态属性(方法). 对象是类的一个实例,是一个具体的事物. 类与对象是抽象与具体的关系. 类其实就是一种数据 ...
- Linux 定时器应用【转】
Linux 定时器应用 实验目的 阅读 Linux 相关源代码,学习 Linux 系统中的时钟和定时器原理,即,ITIMER_REAL实时计数,ITIMER_VIRTUAL 统计进程在用户模式执行的时 ...
- mysql-8.0.11-winx64 免安装版配置方法
mysql-8.0.11-winx64.zip 下载地址:https://dev.mysql.com/downloads/file/?id=476233 mysql-8.0.11-winx64.zi ...