您的位置 首页 java

C语言中,一个函数调用的背后都发生了什么?你了解吗?

我们都知道,在 C语言 中,有个东西叫做函数,任何的高手编写C语言的程序,基本都会用到函数,用函数去实现一个基本的功能,举个例子,实现一个求和的函数,如下:

 int fun_xuanph(int a , intb){
  
 return a+b; 
  
}

int main(){
  
  int a = 1;
  int b = 2;
   printf ("sum=%dn",fun_xuanph(a,b));
}  

上面的程序是那么的简单,虽然简单,但是对于讲解 函数调用 背后的逻辑,足以了,可能有的人看到这个程序第一眼,会觉得这太简单了,不就是一个调用一个函数然后返回一个求和的值吗?我想说,你说得对,就是这么个玩意,但是,你可知道调用一个函数之前,都需要做什么吗?这就涉及到函数入栈的问题,这里要表达一个知识点,那就是任何函数都有自己的栈顶和栈底,那么为什么要有栈呢?

一个程序运行中,之所以要有栈,主要有以下的原因:

1) 保存上下文的环境

我们知道一个函数调用完后,要回到原来的地方去执行,那么就需要保留之前的数据,比如,返回地址, 寄存器 等,这些值会被存到栈中。

2) 局部变量 的值也要保存到栈空间中。

一个函数内部使用的局部变量,也要存到栈中。

现在我们知道,一个函数想要执行,一定要开辟一段内存空间,来存放上下文以及函数内部的局部变量,这块空间就是栈。

我们刚才收到一个函数有自己的栈顶和栈底,那么用什么来表示栈顶和栈底呢?

其实是用两个寄存器来表示,栈顶是esp寄存器,栈底是ebp寄存器,出栈和入栈都会操作esp寄存器,将上面的程序进行 反汇编 ,得到下面的汇编代码,下面就基于汇编程序讲讲函数入栈和出栈的过程:

 000000000040052d <fun_xuanph>:
  40052d:       55                      push   %rbp
  40052e:       48 89 e5                mov    %rsp,%rbp
  400531:       89 7d fc                mov    %edi,-0x4(%rbp)
  400534:       89 75 f8                mov    %esi,-0x8(%rbp)
  400537:       8b 45 f8                mov    -0x8(%rbp),%eax
  40053a:       8b 55 fc                mov    -0x4(%rbp),%edx
  40053d:       01 d0                   add    %edx,%eax
  40053f:       5d                      pop    %rbp
  400540:       c3                      retq

0000000000400541 <main>:
  400541:       55                      push   %rbp
  400542:       48 89 e5                mov    %rsp,%rbp
  400545:       48 83 ec 10             sub    $0x10,%rsp
  400549:       c7 45 f8 01 00 00 00    movl   $0x1,-0x8(%rbp)
  400550:       c7 45 fc 02 00 00 00    movl   $0x2,-0x4(%rbp)
  400557:       8b 55 fc                mov    -0x4(%rbp),%edx
  40055a:       8b 45 f8                mov    -0x8(%rbp),%eax
  40055d:       89 d6                   mov    %edx,%esi
  40055f:       89 c7                   mov    %eax,%edi
  400561:       e8 c7 ff ff ff          callq  40052d <fun_xuanph>
  400566:       89 c6                   mov    %eax,%esi
  400568:       bf 04 06 40 00          mov    $0x400604,%edi
  40056d:       b8 00 00 00 00          mov    $0x0,%eax
  400572:       e8 99 fe ff ff          callq  400410 <printf@plt>
  400577:       b8 00 00 00 00          mov    $0x0,%eax
  40057c:       c9                      leaveq
  40057d:       c3                      retq
  

可以看到第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语言中,一个函数调用的背后都发生了什么?你了解吗?

文章地址:https://www.zhihuclub.com/178313.shtml

关于作者: 智云科技

热门文章

网站地图