图解简单C程序的运行时结构【转载】

程序在内存中的存储分为三个区域,分别是动态数据区、静态数据区和代码区。函数存储在代码区,全局变量以及静态变量存储在静态数据区,而在程序执行的时候才会在动态数据区产生数据。程序执行的本质就是代码区的指令不断执行,驱使动态数据区和静态数据区产生数据变化。
图片发自简书Apphtml

代码区与动态数据区由三个寄存器控制,分别是eip、ebp和esp。eip指向代码区下一个要执行的指令,ebp与esp分别指向动态数据区的栈底和栈顶。初始状况下eip默认指向main函数的第一条指令,esp、ebp指向的位置由程序加载时内核的设置决定。
图片发自简书Appmarkdown

咱们看一下这段代码如何执行的,在执行第一条指令时,考虑到函数调用的问题,ebp会先把当前指向的地址记录到栈中,方便之后返回来继续执行。把地址压进栈时,esp就会自动往栈顶方向移动。说到这里,为避免混淆先科普一下什么是栈顶和栈底,栈只容许在一端作插入和删除操做,这一端就叫栈顶,而另外一端叫作栈底,图中下方叫栈顶,上方叫栈底。esp永远在栈顶,也就是图的最下方。
图片发自简书App函数

因为esp指向的地址已经被记录,那么它就被空闲出来了。如今咱们开始构建main函数的栈,空闲的esp帮忙看着main函数的栈底。这个时候esp与ebp是重叠的。
图片发自简书Apppost

eip继续指向下一条指令,到了局部变量i的初始化,这里将i赋值为4,就将i的初始值压到栈中,esp继续往栈顶移动。下一条指令与本条相同,将局部变量j也压入栈中,如图所示。
图片发自简书App设计

接下来调用了fun函数,虽然fun函数是独立的函数,可是因为是在main函数中调用的,因此依然将数据压至main函数的栈中。fun函数的传入参数为i、j,可是入栈的顺序正好相反,b先入栈,而后a被压入栈中,如图所示。
图片发自简书App3d

接下来要跳转到fun函数了,在跳转以前,咱们要先给fun函数的返回值留个位置,由于要赋值给局部变量m的。而后再将fun函数的返回地址压入栈中,方便执行完fun函数后能继续往下执行。最后再把ebp当前的地址值压入栈中,此时ebp指向的是main函数的栈底(若是这里不作保存,fun函数执行完ebp就回不去了)。
图片发自简书Apphtm

接下来就正式进入了fun函数,像第一次保存完地址值那样,ebp又被闲置了,因此让ebp守住fun函数的栈底。而局部变量b与c的赋值就再也不多说,与main函数的执行过程相同,当走到了return时,将计算出的结果赋值写入到刚空出的返回值那里。
图片发自简书Appblog

此时的fun函数就执行完毕了,咱们要恢复main函数调用fun函数的现场,继续往下执行,要想往下执行,必须将ebp回到main函数的栈底,而且找到fun函数返回的位置,而后跳转到那里。很简单,因为刚才保存了ebp的地址值,因此将地址值赋值给ebp,ebp就指向了main函数的栈底。
图片发自简书App图片

ebp的地址值出栈后,esp就指向了fun函数的返回地址,经过执行ret指令,把该地址值传给eip,使eip指向fun函数执行后的返回地址。
图片发自简书Appip

这样就恢复了现场,而后把fun函数的返回值传递给m,此时局部变量b、a和返回值已经没有价值了,把它们清出栈,如今就剩下干干净净的栈内容了。
图片发自简书App

如今执行最后一步,main函数就结束了,此时局部变量i、j也没有任何做用,作清栈操做,清理出干净的栈空间。
图片发自简书App

以上即是一个简单C程序的运行时结构。本文总结于新设计团队的《编译系统透视:图解编译原理》,图侵删。

转自:https://www.cnblogs.com/enochzzg/p/10693530.html

相关文章
相关标签/搜索