程序员内功修炼——函数栈帧的创建与销毁

一.什么是函数的栈帧

c语言是由函数构成的,那么函数是如何进行传参的?如何调用的?如何返回值的?这些问题与函数的栈帧有关。
函数栈帧:就是函数调用过程中程序的调用栈所开辟的空间,这些空间用来存放:
1.函数参数和返回值
2.临时变量
3.保存上下文信息

二.内存分布

要想明白函数怎么调用内存的,首先得知道内存的分布,如图:
程序员内功修炼——函数栈帧的创建与销毁_第1张图片
知道了内存的分布,下面让我们认识寄存器ebp和esp。
程序员内功修炼——函数栈帧的创建与销毁_第2张图片

ebp esp这2个寄存器是用来存放地址的,同时维护函数的栈帧。每一个函数的创建都要用到函数的栈帧。

三.函数栈帧创建的原理

我们先从一个简单的代码看起

#include
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函数,函数的调用都会在栈区上开辟一块内存空间。
esp:栈指针寄存器,其内存放着一个指针,该指针永远指向栈顶
ebp:内存也放着一个指针,指向栈低。

程序员内功修炼——函数栈帧的创建与销毁_第3张图片
通过调试我们发现函数在创建main函数之前会调用二个函数
程序员内功修炼——函数栈帧的创建与销毁_第4张图片
我们去找到这二个函数
程序员内功修炼——函数栈帧的创建与销毁_第5张图片
程序员内功修炼——函数栈帧的创建与销毁_第6张图片
通过调试我们发现 _tmain函数——>maincr——>main形成这样的调用逻辑关系。main函数开辟空间的时候会先调用前面二个函数,那么调用前二个函数有什么意义呢?
在开始调用前二个函数时候内存也会为它们开辟新的空间
程序员内功修炼——函数栈帧的创建与销毁_第7张图片
我们转到反汇编上,从最底层看看是怎么创建出空间的。
程序员内功修炼——函数栈帧的创建与销毁_第8张图片
这就是上述代码在反汇编下的运行逻辑。我们一步步的分析
程序员内功修炼——函数栈帧的创建与销毁_第9张图片
开始时栈区如上图所示,接着我们看反汇编第一条指令,push意思是压栈
程序员内功修炼——函数栈帧的创建与销毁_第10张图片
运行完第一条指令其结果如下
程序员内功修炼——函数栈帧的创建与销毁_第11张图片
第二条指令mov ,意思是把esp的值给ebp
程序员内功修炼——函数栈帧的创建与销毁_第12张图片
第三条指令sun,意思是给esp的值减上0E4h;esp是地址地址由高到底,减上一个数相当于把esp上移。
程序员内功修炼——函数栈帧的创建与销毁_第13张图片
上述黄色部分就是main函数开辟的空间
程序员内功修炼——函数栈帧的创建与销毁_第14张图片
第4,5,6条指令都是push压栈,3次push给顶上压元素。
程序员内功修炼——函数栈帧的创建与销毁_第15张图片
第7,8,9行指令指把main函数空间全部初识化为cccc
程序员内功修炼——函数栈帧的创建与销毁_第16张图片
这也充分的说明了如果不给局部变量初始化那么其存的是一个随机的值,所以在定义变量的时候一定要给变量初始化。
到目前为止,程序执行的准备工作已经结束,接下来是程序运行阶段我们继续看反汇编
程序员内功修炼——函数栈帧的创建与销毁_第17张图片
这条指令的意思是把ebp的地址向上移8,同时放入值0ah(10进制为10)
程序员内功修炼——函数栈帧的创建与销毁_第18张图片
同理接下来的指令把20放入内存中
程序员内功修炼——函数栈帧的创建与销毁_第19张图片
到这里main函数内部的变量已经定义完成,接下来进入Add函数内部去看看函数是如何进行传参的。
程序员内功修炼——函数栈帧的创建与销毁_第20张图片
第1,2,3条指令说明把ebp-14h放到eax,如何push压栈,接着找到main函数里面放20的地址,把20的地址放到eax,相当于eax里面放了20,同理ecx里面放入10。
程序员内功修炼——函数栈帧的创建与销毁_第21张图片
然后到达call指令,call指令把下一条指令的地址存下,因为进入函数内部实现函数功能,等函数结束后会找到原来函数外部的地址,接着运行代码。

程序员内功修炼——函数栈帧的创建与销毁_第22张图片
和main函数一样前面都是add函数的准备阶段接着要进入add函数内部了。
程序员内功修炼——函数栈帧的创建与销毁_第23张图片
前面和main函数做法类似不在说明
程序员内功修炼——函数栈帧的创建与销毁_第24张图片
首先把ebp-8的值放入eax,相当于eax里面放了10,接着ebp+0ch相当于把ebp+12的值放入eax,eax变为30,加起来后把eax放入ebp减8里面,把30放入ebp-8。接着返回call指令保存的地址。

总结:局部变量怎么创建的?
首先为函数开辟一块空间,再在空间里面为我的局部变量开辟一块小空间。
形参和实参怎么样的?
形参只是形参的一份临时拷贝,二者的空间不同。改变形参不影响实参。
函数调用结果怎么返回的?
通过call指令保存原来的地址,等函数调用结束后返回地址

你可能感兴趣的:(c语言)