c语言函数执行过程(来源老师讲课)
int fun(int a, int b);
int m=10;
int main()
{
int i=4;
int j=5;
m = fun(i,j);
return 0;
}
int fun(int a, int b)
{
int c=0;
c = a+b;
return c;
}
C程序运行的核心是函数的执行和调用。
接下来将介绍内存中函数调用的基本情景,指令如何驱动数据压栈、清栈。
首先我们介绍函数调用的内存中的三个区域(如上图):
- 代码区
- 代码区装载了这个程序所对应的机器指令
- 程序的执行就靠这些指令来驱动
- 静态数据区
- 装载了全局变量m的数值10
- 后面程序的执行会改变这里的值
- 动态数据区
- 初始什么都没有,因为只有程序执行后,在指令的驱动下,这个区域才会产生数据。
- 压栈、清栈的工作就是在这个区域完成的。
- 程序的执行会导致动态、静态数据区中数据的变化,知道执行完毕。
CPU中有三个寄存器,分别是eip
、ebp
、esp
eip
永远指向代码区将要执行的下一条指令,执行方式有两种- 顺序执行,执行完一条指令跳转到下一条指令
- 跳转,执行时eip跳转到指定的位置执行指令
ebp
和esp
用于管理栈空间,ebp
指向栈底,esp
指向栈顶,代码区中的函数调用、返回和执行都伴随着不断的压栈和清栈。
栈中数据存储和释放的原则:后进先出
main函数栈的构建如下图
初始情况,eip
指向main函数的第一条指令,程序还未执行,栈空间也还没有数据,ebp
和esp
指向的位置十四程序加载时内核设置的。
在程序开始执行main函数的第一条指令时,eip
自动指向下一条指令,ebp
会把初始ebp
地址保存在栈中,目的是本程序执行完毕后,ebp
还能回到现在的位置使现在的栈复原,esp
会自动向栈顶方向移动,它永远指向栈顶。
eip
继续执行会开始构建main函数自己的栈,ebp
会看管(指向)main的栈底。main函数建立成功时,实质性的代码还没有运行,栈中没有内容,栈顶、栈底重合,ebp
、esp
指向同一个给位置。
接下来继续执行局部便令i、j
的初始化,4、5两个数值会存到栈中,esp会继续移动到栈顶,两个局部变量都是属于main函数的。
接下来调用fun函数时,用到的数据压入main函数栈中,但是这是由fun函数使用的。
fun要使用到的数据一部分在自己的栈中,一部分在main函数的栈中。
main函数会继续执行传参指令,参数入栈的顺序和书写的顺序正好相反,也就是先压入5,再压入4。
接下来程序会为fun函数的返回值开辟一块空间,将来fun函数执行完毕返回时得到的返回值会存储到这里并且传递给全局变量m。
接下来跳转到fun函数执行,这会分为两个部分,一部分把fun函数执行后的返回地址压入栈中,以便fun执行完毕后能够返回到main函数中执行。到这里函数调用数据准备工作完成。另一部分时跳转到被调函数的第一条指令去执行。
fun函数执行过程如图
首先要保存ebp
指向的地址值(此时还在main函数栈底),目的时返回时可以恢复main函数栈底的位置。
接下来构建fun函数的栈,初始化结束后,ebp
和esp
都指向fun函数的栈底。
继续执行会初始化局部变量为0,压入栈中。这里我们可以看到fun函数要用的数据:局部变量存在fun函数栈中,参数存在main函数栈中。
通过以ebp
为基点的寻址,很容易找到main和fun函数中的数据,且可以计算得到c=9
。
继续执行,局部变量c中的数据会当作返回值返回到之前为之开辟出来的位置,fun函数执行完毕。
收尾工作(恢复现场)
恢复main函数调用fun的现场。
- main函数的栈要恢复(包括栈顶、栈底)
- 找到fun函数执行后返回地址并返回到main函数中继续执行
首先是ebp
,栈中之前存储了ebp
的地址,会把地址回传给ebp
,然后ebp
会指回main函数栈底。
栈会消栈,栈顶这时会移动到func执行后的返回地址
位置,接下来会执行RET
指令,此指令会自动把栈顶值给eip
,使eip
指向fun函数执行后的返回地址,此时恢复现场(main调用fun的过程)完毕,fun函数的返回值会传给全局变量m。
接下来,处理调用fun时穿的参数,对于main来说参数没有存在的必要,需要清栈。
思路还是不太清晰,希望各位多批评指正