我们都知道,在C语言中,有个东西叫做函数,任何的高手编写C语言的程序,基本都会用到函数,用函数去实现一个基本的功能,举个例子,实现一个求和的函数,如下: int fun_xuanph(int a , intb){ return a+b; }int main(){ int a = 1; int b = 2; printf('sum=%d\n',fun_xuanph(a,b));} 上面的程序是那么的简单,虽然简单,但是对于讲解函数调用背后的逻辑,足以了,可能有的人看到这个程序第一眼,会觉得这太简单了,不就是一个调用一个函数然后返回一个求和的值吗?我想说,你说得对,就是这么个玩意,但是,你可知道调用一个函数之前,都需要做什么吗?这就涉及到函数入栈的问题,这里要表达一个知识点,那就是任何函数都有自己的栈顶和栈底,那么为什么要有栈呢? 一个程序运行中,之所以要有栈,主要有以下的原因: 1) 保存上下文的环境 我们知道一个函数调用完后,要回到原来的地方去执行,那么就需要保留之前的数据,比如,返回地址,寄存器等,这些值会被存到栈中。 2) 局部变量的值也要保存到栈空间中。 一个函数内部使用的局部变量,也要存到栈中。 现在我们知道,一个函数想要执行,一定要开辟一段内存空间,来存放上下文以及函数内部的局部变量,这块空间就是栈。 我们刚才收到一个函数有自己的栈顶和栈底,那么用什么来表示栈顶和栈底呢? 其实是用两个寄存器来表示,栈顶是esp寄存器,栈底是ebp寄存器,出栈和入栈都会操作esp寄存器,将上面的程序进行反汇编,得到下面的汇编代码,下面就基于汇编程序讲讲函数入栈和出栈的过程:
可以看到第15行,将esp向下移动了16个字节,实际上这就是为main函数开辟的栈空间,这16个字节,存放的是局部变量a,局部变量b,以及调用fun_xuanph函数时,下一条指令的地址,如下图所示: main函数栈空间 通过上图我们知道,rsp指向的是下一条指令的下面,这是由call指令产生的,在调用call指令时,会被call指令的下一条指令的地址进行入栈,因此rsp往下走8个字节,存下一条指令的地址,接下来,我们再看fun_xuanph函数的栈空间: fun_xuanph函数的栈空间 可以看到,汇编语言的第二行,调用了push rbp ,将rbp寄存器压入了栈中,进行保存,紧接着又调用了mov rsp,rbp 将rsp赋值给了rbp寄存器,此时rbp就是函数fun_xuanph的栈底,rsp就是fun_xuanph函数的栈顶。 |
|
来自: 山峰云绕 > 《C语言数据结构描述Windows程序设计》