图解简单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程序的运行时结构。本文总结于新设计团队的《编译系统透视:图解编译原理》,图侵删。

原文地址:https://www.cnblogs.com/enochzzg/p/10693530.html

时间: 2024-10-09 10:15:01

图解简单C程序的运行时结构的相关文章

内存的划分 & 程序代码运行时内存工作流程

内存的划分: 1,寄存器. 2,本地方法区. 3,方法区. 4,栈内存. 存储的都是局部变量. 而且变量所属的作用域一旦结束,该变量就自动释放. 5,堆内存. 存储是数组和对象(其实数组就是对象) ,凡是new建立的都在堆中. 特点: 1)每一个实体都有首地址值. 2)堆内存中的每一个变量都有默认初始化值,根据类型的不同而不同.整数是0,小数0.0或者0.0f,boolean类型是false,char类型是 '\u0000',引用数据类型是NULL 3)垃圾回收机制. 全局变量和局部变量的区别:

[快手(AAuto)学习笔记]如何让程序在运行时请求管理员权限(UAC)

作者:ffsystem 作为(糟糕的)程序猿,习惯写代码解决一些简单事务.正常用批处理就能解决大部分工作,复杂一点用AutoIt 3. 有时候要分发给别人,就需要一个界面.外行你程序写得如何他看不懂,也不想搞懂.他只看你界面是否花哨,来判断你行不行.没办法只是个看脸的时代,只好给程序整整容. 但是简单的事情,上VS.用.net:或者用java就太无趣了,而且那玩意整好看一点也麻烦. AutoIt3很好用,但界面,但比较传统,找到一个国产的程序,快手AAuto,好像既轻量级.又能html给程序整容

java拾遗4----一个简单java程序的运行全过程

简单说来,一个java程序的运行需要编辑源码.编译生成class文件.加载class文件.解释或编译运行class中的字节码指令. 下面有一段简单的java源码,通过它来看一下java程序的运行流程: 1 class Person 2 3 { 4 5 private String name; 6 7 private int age; 8 9 10 11 public Person(int age, String name){ 12 13 this.age = age; 14 15 this.na

JVM的运行时结构

程序计数器(Program Counter Register):是一块较少的内存空间(线程私有的),它可以看作是当前线程所指向的字节码的行号指示器. Java虚拟机栈(Java Virtual Machine Stacks):与程序计数器一样,它也是线程私有的,它的生命周期与线程相同.虚拟机栈描述的是Java方法执行的内存模型:每个方法在执行的同时都会创建一个栈帧(Stack Frame)用于存储局部变量表.操作数栈.动态链接.方法出入口灯信息.美一个方法从调用直至执行完成的过程,就对应一个栈帧

通过编写c语言程序,运行时实现打印另一个程序的源代码和行号

2017年6月1日程序编写说明: 1.实现行号的打印,实现代码的读取和输出,理解主函数中的参数含义. 2.对fgets函数理解不够 3.对return(1); return 0的含义理解不够 4.未实现页码的打印 1 /************************************************************************* 2 > File Name: my_print_program.c 3 > Author: Mr.Yang 4 > Purp

了解程序内部运行时的状态

阅读源码的方式:任何一个程序只有处于运行状态时,才能突显出其内在的价值.应该从程序运行角度来看问题,而不是死板地对着源代码本身. 日志输出和调试源码是学习框架运行机制的最好方法. 调试源码:加入jar文件到项目的CLASSPATH中,为加入到CLASSPATH中的这些jar文件附上其所对应的源代码.右键单击jar文件,选择Properties选项卡,弹出jar文件的属性选项框,选择选项卡左侧的“Java Source Attachment”选项,点击输入框右侧的按钮,指定相应的源代码所在的目录或

NLOG在Console程序中运行时不起作用

把Web程序中的NLog.dll, NLog.config, NLog.xsd拷到Console程序中,发现运行不起作用,看了网上的示例教程,如http://www.cnblogs.com/sorex/archive/2013/01/31/2887174.html,试了下,多了个packages.config文件,里面内容如下: <?xml version="1.0" encoding="utf-8"?> <packages> <pac

编写一个程序,此程序在运行时要求用户输入一个 整数,代表某门课的考试成绩,程序接着给出“不及格”、“及格”、“中”、“良”、“优”的结论。要求程序必须具备足够的健壮性,不管用户输入什 么样的内容,都不会崩溃。

import java.util.*; class AException extends Exception { String a; AException() { a="输入有误"; } public String toString() { return a; } } class A { public static void main(String args[]) { while(1>0) { Scanner sc = new Scanner(System.in); System

iOS开发之runtime运行时机制

最近参加三次面试都有被问到runtime,也不知道面试官们自己懂不懂,反正就喜欢问,似乎就想把我唬住,来显示自己的对OC底层知识的理解很牛逼似的.(我现在开始看面试题,发现他们都喜欢问面试题的问题,我算明白了,都是应试教育惹的祸,大概他们以前没什么实际的app经验,所以准备了很多面试题,而我真的是能写app,先干活再找方法的人,面试不多,没什么面试经验,看来要开始碰壁多次才能掌握中国式面试).因为不太懂runtime我就只能支支吾吾的说点零碎.我真的好几次努力想看一看runtime的知识,因为知