C语言运行时栈

1.调用函数的过程

C语言中调用一个没有参数的空函数的过程为:保存%rbp的值;nop;恢复%rbp的值。

   #include <stdio.h>                                                              
                                                                                   
   void nop();                                                                     
                                                                                   
   int main()                                                                      
   {                                                                               
       nop();                                                                      
   }                                                                               
                                                                                   
  void nop()                                                                      
  {                                                                               
                                                                                  
  }                                                                               

以上空代码,汇编的结果为:

0000000000000000 <main>:
   0:	55                   	push   %rbp
   1:	48 89 e5             	mov    %rsp,%rbp
   4:	b8 00 00 00 00       	mov    $0x0,%eax
   9:	e8 00 00 00 00       	callq  e <main+0xe>
   e:	b8 00 00 00 00       	mov    $0x0,%eax
  13:	5d                   	pop    %rbp
  14:	c3                   	retq   

0000000000000015 <nop>:
  15:	55                   	push   %rbp
  16:	48 89 e5             	mov    %rsp,%rbp
  19:	90                   	nop
  1a:	5d                   	pop    %rbp
  1b:	c3                   	retq   

可以看到,调用nop函数时先保存了%rbp的值,将栈指针的值赋值给%rbp,nop,取出保存的值,退出;

当nop函数中有参数时:

   #include <stdio.h>                                                              
                                                                                   
   void nop(int a);                                                                     
                                                                                   
   int main()                                                                      
   {                                                                               
       nop(1);                                                                                                                                                                                
   }                                                                               
                                                                                   
   void nop(int a)                                                                 
   {                                                                               
      a++;                                                                          
   }

汇编的结果为:

0000000000000000 <main>:
   0:	55                   	push   %rbp
   1:	48 89 e5             	mov    %rsp,%rbp
   4:	bf 01 00 00 00       	mov    $0x1,%edi
   9:	b8 00 00 00 00       	mov    $0x0,%eax
   e:	e8 00 00 00 00       	callq  13 <main+0x13>
  13:	b8 00 00 00 00       	mov    $0x0,%eax
  18:	5d                   	pop    %rbp
  19:	c3                   	retq   

000000000000001a <nop>:
  1a:	55                   	push   %rbp
  1b:	48 89 e5             	mov    %rsp,%rbp
  1e:	89 7d fc             	mov    %edi,-0x4(%rbp)
  21:	83 45 fc 01          	addl   $0x1,-0x4(%rbp)
  25:	90                   	nop
  26:	5d                   	pop    %rbp
  27:	c3                   	retq   

可以看到,参数通过参数寄存器%edi传递到nop函数中,并将参数保存到了栈指针 -4的空间,调用时也从这里调用,从这里我们可以看到增加参数的时候,参数保存的地址是减小的。

x86共有6个参数寄存器,也就是说,前6个参数通过寄存器传递,之后的参数直接push到栈里:


   #include <stdio.h>                                                              
                                                                                   
   void nop(int a, int b, int c, int d, int e, int f, int g, int h);               
                                                                                   
   int main()                                                                      
   {                                                                               
       nop(1, 2, 3, 4, 5, 6, 7, 8);                                                                                                                                                           
   }                                                                               
                                                                                   
  void nop(int a, int b, int c, int d, int e, int f, int g, int h)                
  {                                                                               
      a++;
      g++;
      h++;                                                                          
  }      

汇编结果:

0000000000000000 <main>:
   0:	55                   	push   %rbp
   1:	48 89 e5             	mov    %rsp,%rbp
   4:	6a 08                	pushq  $0x8
   6:	6a 07                	pushq  $0x7
   8:	41 b9 06 00 00 00    	mov    $0x6,%r9d
   e:	41 b8 05 00 00 00    	mov    $0x5,%r8d
  14:	b9 04 00 00 00       	mov    $0x4,%ecx
  19:	ba 03 00 00 00       	mov    $0x3,%edx
  1e:	be 02 00 00 00       	mov    $0x2,%esi
  23:	bf 01 00 00 00       	mov    $0x1,%edi
  28:	e8 00 00 00 00       	callq  2d <main+0x2d>
  2d:	48 83 c4 10          	add    $0x10,%rsp
  31:	b8 00 00 00 00       	mov    $0x0,%eax
  36:	c9                   	leaveq 
  37:	c3                   	retq   

0000000000000038 <nop>:
  38:	55                   	push   %rbp
  39:	48 89 e5             	mov    %rsp,%rbp
  3c:	89 7d fc             	mov    %edi,-0x4(%rbp)
  3f:	89 75 f8             	mov    %esi,-0x8(%rbp)
  42:	89 55 f4             	mov    %edx,-0xc(%rbp)
  45:	89 4d f0             	mov    %ecx,-0x10(%rbp)
  48:	44 89 45 ec          	mov    %r8d,-0x14(%rbp)
  4c:	44 89 4d e8          	mov    %r9d,-0x18(%rbp)
  50:	83 45 fc 01          	addl   $0x1,-0x4(%rbp)
  54:	83 45 10 01          	addl   $0x1,0x10(%rbp)
  58:	83 45 18 01          	addl   $0x1,0x18(%rbp)
  5c:	90                   	nop
  5d:	5d                   	pop    %rbp
  5e:	c3                   	retq   

可以看到前6个参数通过寄存器传递,7,8俩个参数直接push到栈里,通过调用俩个参数可以发现,先压入栈中的参数7、8在高地址,且参数8的地址比参数7更高。参数的栈排布为:

栈地址参数
188(h)
107 (g)
-41 (a)
-82 (b)
-c3 (c)
-104 (d)
-145 (e)
-186 (f)

局部变量也保存在栈中:

   #include <stdio.h>                                                              
                                                                                   
   void nop(int a, int b, int c, int d, int e, int f, int g, int h);               
                                                                                   
   int main()                                                                      
   {                                                                               
       nop(1, 2, 3, 4, 5, 6, 7, 8);                                                
   }                                                                               
                                                                                   
  void nop(int a, int b, int c, int d, int e, int f, int g, int h)                
  {                                                                               
      int var0 = 0;                                                               
      int var1 = 1;                                                               
      int var2 = 2;                                                               
      int var3 = 3;                                                               
      int var4 = 4;                                                                                                                                                                          
  }     

汇编结果:

0000000000000000 <main>:
   0:	55                   	push   %rbp
   1:	48 89 e5             	mov    %rsp,%rbp
   4:	6a 08                	pushq  $0x8
   6:	6a 07                	pushq  $0x7
   8:	41 b9 06 00 00 00    	mov    $0x6,%r9d
   e:	41 b8 05 00 00 00    	mov    $0x5,%r8d
  14:	b9 04 00 00 00       	mov    $0x4,%ecx
  19:	ba 03 00 00 00       	mov    $0x3,%edx
  1e:	be 02 00 00 00       	mov    $0x2,%esi
  23:	bf 01 00 00 00       	mov    $0x1,%edi
  28:	e8 00 00 00 00       	callq  2d <main+0x2d>
  2d:	48 83 c4 10          	add    $0x10,%rsp
  31:	b8 00 00 00 00       	mov    $0x0,%eax
  36:	c9                   	leaveq 
  37:	c3                   	retq   

0000000000000038 <nop>:
  38:	55                   	push   %rbp
  39:	48 89 e5             	mov    %rsp,%rbp
  3c:	89 7d dc             	mov    %edi,-0x24(%rbp)
  3f:	89 75 d8             	mov    %esi,-0x28(%rbp)
  42:	89 55 d4             	mov    %edx,-0x2c(%rbp)
  45:	89 4d d0             	mov    %ecx,-0x30(%rbp)
  48:	44 89 45 cc          	mov    %r8d,-0x34(%rbp)
  4c:	44 89 4d c8          	mov    %r9d,-0x38(%rbp)
  50:	c7 45 ec 00 00 00 00 	movl   $0x0,-0x14(%rbp)
  57:	c7 45 f0 01 00 00 00 	movl   $0x1,-0x10(%rbp)
  5e:	c7 45 f4 02 00 00 00 	movl   $0x2,-0xc(%rbp)
  65:	c7 45 f8 03 00 00 00 	movl   $0x3,-0x8(%rbp)
  6c:	c7 45 fc 04 00 00 00 	movl   $0x4,-0x4(%rbp)
  73:	90                   	nop
  74:	5d                   	pop    %rbp
  75:	c3                   	retq   

可以看出,局部变量越多,参数越多,最终会导致栈的地址越来越小,即栈是向低地值增长的。进入函数后调用栈的情况为:

高地址push到栈中的参数
……局部变量
低地值通过寄存器传递的参数

2. 运行时栈

经过以上的内容已经了解到了函数调用时发生了什么,结合进程整个程序的栈帧就可以得出运行时的栈帧以及堆在程序中的变化:

#include <stdio.h>                                                                                                                                                                             
#include <stdlib.h>                                                                
                                                                                   
void nop();                                                                        
                                                                                   
int main()                                                                         
{                                                                                  
    int arg = 0;                                                                   
    char *pointer = (char *)malloc(1);                                             
    printf("arg addr in main %p\n", &arg);                                         
    printf("pointer in main %p\n", pointer);                                       
    nop();                                                                         
    free(pointer);                                                                 
}                                                                                  
                                                                                   
void nop()                                                                         
{                                                                                  
    int arg = 0;                                                                   
    char *pointer = (char *)malloc(1);                                             
    printf("arg addr in nop %p\n", &arg);                                          
    printf("pointer in nop %p\n", pointer);                                       
    free(pointer);                                                                 
}  

运行结果:

arg addr in main 0x7ffe491e420c
pointer in main 0x55b3d8bfa010
arg addr in nop 0x7ffe491e41dc
pointer in nop 0x55b3d8bfa440

可以看到:

低地值main pointer
……nop pointer
……未使用的空间
……nop arg
高地址main arg

由此,栈的由高地址向低地值减少,堆由低地址向高地址增加,且如果先在nop函数中malloc并且不释放然后在main中malloc,nop会获得一个更小的地址,但是栈的地址不会根据执行顺序改变,因为栈的空间在编译的时候已经分配好了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值