了解不同变量在内存中的存储布局,对于程序员是很重要的,我们看下面一段程序:

#include <stdio.h>

const int A = 10;
int a = 20;
static int b = 30;
int c;

int main(void)
{
 static int a = 40;
 char b[] = "Hello world";
 register int c = 50;

 printf("Hello world %d\n", c);

 return 0;
}

我们编译之后用readelf命令看它的符号表,了解各变量的地址分布,具体操作如下:

我们截取我们所关心的部分:

   Num:    Value          Size Type    Bind   Vis      Ndx Name

    12: 0000000000004014     4 OBJECT  LOCAL  DEFAULT   25 b
    13: 0000000000004018     4 OBJECT  LOCAL  DEFAULT   25 a.0
    31: 0000000000002004     4 OBJECT  GLOBAL DEFAULT   18 A
    34: 0000000000004020     4 OBJECT  GLOBAL DEFAULT   26 c
    35: 0000000000004010     4 OBJECT  GLOBAL DEFAULT   25 a

Section Headers:
  [Nr] Name     Type      Address        Offset      Size                  
  
[16] .text   PROGBITS 0000000000001080  00001080 0000000000000154 
[18] .rodata PROGBITS 0000000000002000  00002000 0000000000000018 
[25] .data   PROGBITS 0000000000004000  00003000 000000000000001c  
[26] .bss    NOBITS   000000000000401c  0000301c 000000000000000c 

(1)变量A用const修饰,可以看到其位于.rodata段,我们可以通过hexdump -C a.out

可以看到字符串”hello world”也分配在rodata段,说明字符串字面值是只读的,相当于在全局作用域定义了一个const数组

(2)而.data段可以看到从0000000000004000开始,size是1C,也就是到000000000000401C结束,在这个范围内,我们有三个变量:a, b, a.0

a是一个GLOBAL的符号,而b被static关键字修饰了,导致它成为一个LOCAL的符号,所以static在这里的作用是声明b这个符号为LOCAL的,不被链接器处理,链接器会在之后文章进行说明。总之也就是说,一个函数定义前面也可以用static修饰,表示这个函数名符号是LOCAL的, a.0是什么呢?它就是main函数中的static int a。

  • 函数中的static变量不同于以前我们讲的局部变量,它并不是在调用函数时分配,在函数返回时释放,而是像全局变量一样静态分配

  • 函数中的static变量只在函数中起作用,比如main函数中的a这个变量名只在main函数中起作用,在别的函数中说变量a就不是指它了,所以编译器给它的符号名加了一个后缀,变成a.0,以便和全局变量a以及其它函数的变量a区分开

(4)bss段从000000000000401c开始,仅有一个变量c,在这个段,进程会为bss段开辟内存空间,并清零

(5)现在还有register int c = 50 以及 char b[] = “Hello world”没有分析。而函数的参数和局部变量是分配在栈上的。我们来查看反汇编代码:

  • 可见,给b初始化用的这个字符串"Hello world"并没有分配在.rodata段,而是直接写在指令里了,通过三条指令把12个字节写到栈上

  • 变量c并没有在栈上分配存储空间,而是直接存在eax寄存器里,后面调用printf也是直接从eax寄存器里取出c的值当参数压栈,这就是register关键字的作用,指示编译器尽可能分配一个寄存器来存储这个变量

总结:

  • bss段:用来存放程序中未初始化的全局变量的内存区域,属于静态内存分配

  • data段:用来存放程序中已初始化的全局变量的内存区域,属于静态内存分配

  • text段:用来存放程序执行代码的内存区域

  • rodata段:存放只读数据,比如字符串常量、全局const变量(注意并非所有常量都会被保存在rodata段中)

  • 栈:用户程序临时创建的局部变量

  • 堆:用于存放进程运行中被动态分配的内存段,它的大小并不固定,可动态扩张或缩减, 需要通过new,malloc分配自己手动申请, 通过free自己手动释放

变量的存储布局https://mp.weixin.qq.com/s/FlJ7KdgIRxk6vAk6ueINFg

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐