有关函数调用机制和栈帧,借助一段代码说明
#include<stdio.h>
long add(long a, long b){
long x = a,y = b;
return (x + y);
}
int main(){
long a =1,b = 2;
printf("%d\n",add(a,b));
return 0;
}
在此之前,我们先提一下栈。
栈 是一种数据结构,它的特点是“先进后出” ,看图:
这时候有两个东西要介绍一下,EBP和ESP。这是寄存器的两个区域,暂时把他们理解成指针。简单说,EBP始终指向栈底,ESP始终指向栈顶。
大致知道栈长什么样了,接下来介绍正题
程序在运行时会把c语言转换成汇编代码,再转换成机器码,我们现在用汇编代码看看调用函数时计算机究竟干了什么。我们来一步步走代码:
程序运行开始,系统会给我们加一些启动代码以及调用一些API函数,当然在此不做介绍。然后,程序运行到 main函数。调用 main函数的汇编指令为 call A(假设A是main函数的地址)。在执行call指令的时候,会先把main函数的返回地址压到栈里,执行PUSH EBP将栈底的地址压入栈里保存待用,再执行MOV EBP ,ESP令栈底指针指向栈顶。然后代码执行到a=1,b=2,ESP会向上移动(地址偏移),
为局部变量a和b开辟一块栈区空间,如图:
(弄的有点难看啦,哈哈)这样EBP和ESP又新组成了一块空间(上图空白的部分假设装的是别的一些东西)这块新开辟的空间就叫做main函数的栈帧。为了保护栈不会崩溃以及函数可以有序执行,栈帧技术是很重要的。同理,当执行到printf函数时,还会形成一个栈帧,用来保护main栈帧里的数据,EBP和ESP同样会移动。当然EBP指向的就该是printf栈帧的栈底,可以看到在printf函数里又调用了add函数,同样是先保护现场,为add里的局部变量x和y开辟栈区空间,当运算完成后,将结果保存到EAX寄存器中,然后执行MOV ESP,EBP 把栈顶指针下移到栈底,换句话说就是释放此add函数栈帧空间。当然里面的变量和数据就不用管了,因为下次形成栈帧时会覆盖掉。然后执行POP EBP 把EBP恢复到栈帧底部保存过的EBP值,此时ESP也会下移,最后执行RETN语句,跳转到栈帧保存的返回地址,此时被调用函数就执行完了,add,printf,main,还有系统的API函数都是如此,都是经过栈帧保护数据,保存变量,参数,返回地址等信息。这样,一层一层,程序的稳定性就得到了保证。
本人才疏学浅,第一次写博客,如果有错,还请各位大佬指出,可以私信我,感激不尽!
来源:CSDN
作者:No end
链接:https://blog.csdn.net/weixin_43736741/article/details/104755620