C语言运行时栈

1.调用函数的过程

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

   #include                                                               
                                                                                   
   void nop();                                                                     
                                                                                   
   int main()                                                                      
   {                                                                               
       nop();                                                                      
   }                                                                               
                                                                                   
  void nop()                                                                      
  {                                                                               
                                                                                  
  }                                                                               

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

0000000000000000 
: 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 e: b8 00 00 00 00 mov $0x0,%eax 13: 5d pop %rbp 14: c3 retq 0000000000000015 : 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                                                               
                                                                                   
   void nop(int a);                                                                     
                                                                                   
   int main()                                                                      
   {                                                                               
       nop(1);                                                                                                                                                                                
   }                                                                               
                                                                                   
   void nop(int a)                                                                 
   {                                                                               
      a++;                                                                          
   }

汇编的结果为:

0000000000000000 
: 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 13: b8 00 00 00 00 mov $0x0,%eax 18: 5d pop %rbp 19: c3 retq 000000000000001a : 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                                                               
                                                                                   
   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 
: 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 2d: 48 83 c4 10 add $0x10,%rsp 31: b8 00 00 00 00 mov $0x0,%eax 36: c9 leaveq 37: c3 retq 0000000000000038 : 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更高。参数的栈排布为:

栈地址 参数
18 8(h)
10 7 (g)
-4 1 (a)
-8 2 (b)
-c 3 (c)
-10 4 (d)
-14 5 (e)
-18 6 (f)

局部变量也保存在栈中:

   #include                                                               
                                                                                   
   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 
: 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 2d: 48 83 c4 10 add $0x10,%rsp 31: b8 00 00 00 00 mov $0x0,%eax 36: c9 leaveq 37: c3 retq 0000000000000038 : 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                                                                                                                                                                              
#include                                                                 
                                                                                   
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会获得一个更小的地址,但是栈的地址不会根据执行顺序改变,因为栈的空间在编译的时候已经分配好了。

你可能感兴趣的:(深入理解计算机系统笔记,C语言,汇编,调用栈)