(栈帧和函数调用一)栈帧,函数调用与栈的关系

(栈帧和函数调用一)栈帧,函数调用与栈的关系

     在计算机科学中,栈是一个特殊的容器,用户可以将数据压入栈中(入栈,push),也可以将已经压入栈中的数据弹出(出栈,pop),但栈容器必须准守一个规则:先入栈的数据后出栈(First In Last Out,FIFO)。
     在计算机系统中,栈是一个具有以上属性的动态内存区域(这里的动态指的是栈的空间可以动态增长或减小)。程序可以将数据压入栈中,也可以将数据从栈顶弹出。压栈使得栈空间增大,出栈使得栈空间减小。栈的空间总是自顶向下增长的。

一,栈帧的介绍

     栈在程序空间中有着非常重要的地位,栈中保存了一个函数调用所需的维护信息,这就是我们常说的栈帧,栈帧一般包括以下几方面内容:
1) 函数的返回地址(用于返回函数调用处)和参数
2) 临时变量:包括函数的非静态局部变量以及编译器自动生成的其他临时变量
3) 保存的上下文:包括在函数调用前后需要保持不变的寄存器。
     在i386中,一个函数的活动记录范围由ebp和esp两个寄存器来
划定,其中esp寄存器始终指向栈顶,同时也指向当前函数的活动记录的顶部;ebp寄存器指向栈底。ebp又被称为帧指针,esp被称为栈指针。
     栈帧是是相对于每一个函数调用而言的,是独立的。

二,函数调用与栈的关系

在这里插入图片描述
     一个函数的调用分为以下几步;
1) 将函数参数按照函数定义约定依次压入栈中(_stdcall,_cdcel等)
2) 将当前指令的下一条指令(也就是函数返回地址)压入栈中
3) 跳转到函数体执行函数
4) 栈帧调整:
a) Push ebp:将ebp压入栈中,(old ebp)
b) Move sbp,esp:将esp的值赋给ebp,这时ebp指向栈顶位置,而栈顶就是old ebp
c) Sub esp,XXX栈上分配临时空间,push XXX/pop XXX保存某些寄存器的值
d) mov eax, xxx:保存被调用函数的返回值到寄存器eax中
e) move esp,ebp:恢复esp栈顶指针,同时回收栈上的局部变量
f) pop ebp:恢复ebp栈底指针
g) ret:从栈中取出返回地址,并jump到该地址

三,汇编演示

     以一个简单的函数为例:

Int fun()
{
	return 1;
}

     对该函数反汇编结果如下:(vs编译器调试在函数入口处打断点,调试-窗口-反汇编)
在这里插入图片描述
     第1-2行,保存了旧的ebp,并让ebp指向当前的栈顶。
     第3行,将esp栈顶扩展了0XC0个字节,这一大段空出来的空间用来存储局部变量,临时数据或者调试信息。
     第4-6行,将ebx,esi,edi三个寄存器的值保存到了栈上,这三个寄存器的值在函数随后的执行中可能被修改,所以要先保存一下这些寄存器原本的值,以便退出函数时恢复。
     第7-12行,也就是return 1之前是一些调试信息,其中请注意有一步是将eax赋值为0xCCCCCCCC,而eax是用来接受函数返回值的寄存器。
     第13行,将函数返回值1赋值给eax寄存器。
     第14-16行,从栈上恢复ebx,esi,edi三个寄存器的值
     第17行,将esp值加上0XC0个字节,恢复到原先的栈顶
     第18-21行,ebp出栈,恢复原先的栈底
     第22行,使用ret指令返回。

四,总结

1,栈帧是一个函数活动的记录,通过栈帧我们可以进行函数调用参数传递,以及函数返回值调用路径的回溯。
2,函数的返回值是保存在eax寄存器中,在i386中,它是一个32位的寄存器,占4个字节(那么大于4个字节的函数返回值如何传递呢?本系列文章后续揭晓)
3,函数调用的过程中,有进行栈的扩容操作,用于存放函数的入参,临时变量等一些信息,当函数调用结束后,这一部分栈空间会通过栈顶指针(esp寄存器)的移动被回收掉。

参考文献:
1,《程序员的自我修养:链接、装载与库》第十章第二节

发布了78 篇原创文章 · 获赞 79 · 访问量 6万+

猜你喜欢

转载自blog.csdn.net/bajianxiaofendui/article/details/102853629