在解释bss段与data段区别前,先来看下他们定义,以及内存中的位置。
虚拟地址空间
在32位x86的Linux系统中,虚拟地址空间布局如下图所示:
虚拟地址空间分布
- bss段(bss segment):bss是Block Started by Symbol的简称,用来存放程序中未初始化的全局变量的内存区域,属于静态内存分配。
- data段(data segment):用来存放程序中已初始化的全局变量的内存区域,属于静态内存分配。
- text段(text segment):用来存放程序执行代码的内存区域。这部分区域的大小在程序运行前就已经确定,并且内存区域通常属于只读(某些架构也允许代码段为可写,即允许修改程序)。也有可能包含一些只读的常数变量,例如字符串常量等。
- 堆(heap):用于存放进程运行中被动态分配的内存段,它的大小并不固定,可动态扩张或缩减。
- 栈(stack):用户存放程序临时创建的局部变量,也就是说我们函数括弧“{}”中定义的变量(但不包括static声明的变量,static意味着在data段中存放变量)。除此以外,在函数被调用时,其参数也会被压入发起调用的进程栈中,并且待到调用结束后,函数的返回值也会被存放回栈中。
上面只是简单介绍了各个内存段,如果想详细了解,可以看这篇文章《深度解析内存中的程序》。
bss段与data段的区别
在初始化时 bss 段部分将会清零。bss 段属于静态内存分配,即程序一开始就将其清零了。
比如,在C语言之类的程序编译完成之后,已初始化的全局变量保存在.data 段中,未初始化的全局变量保存在.bss 段中。
- text 和 data 段都在可执行文件中,由系统从可执行文件中加载;
- 而 bss 段不在可执行文件中,由系统初始化。
上面这些理论可能有些抽象,下面我们通过示例代码来对比两者区别。
示例代码
#include <stdio.h>
int bss_1;// 未初始化的全局变量,bss段
int bss_2 = 0;// 初始化为0的全局变量,bss段
int data_1 = 1;// 初始化非0的全局变量,data段
int main() {
static int bss_3;// 未初始化的静态局部变量,bss段
static int bss_4 = 0;// 初始化为0静态局部变量,bss段
static int data_2 = 1;// 初始化非0静态局部变量,data段
printf("bss段地址:bss_1 = %p\n", &bss_1);
printf("bss段地址:bss_2 = %p\n", &bss_2);
printf("bss段地址:bss_3 = %p\n", &bss_3);
printf("bss段地址:bss_4 = %p\n\n", &bss_4);
printf("data段地址:data_1 = %p\n", &data_1);
printf("data段地址:data_2 = %p\n", &data_2);
return 0;
}
使用 objdump -t
反汇编查看变量的存储位置。
gcc bss_data_test.c -o bss_data_test
objdump -t bss_data_test | grep bss_
bss_data_test: 文件格式 elf64-x86-64
0000000000000000 l df *ABS* 0000000000000000 bss_data_test.c
0000000000601048 l O .bss 0000000000000004 bss_3.2288
000000000060104c l O .bss 0000000000000004 bss_4.2289
0000000000601044 g O .bss 0000000000000004 bss_2
0000000000601040 g .bss 0000000000000000 __bss_start
0000000000601050 g O .bss 0000000000000004 bss_1
objdump -t bss_data_test | grep data_
bss_data_test: 文件格式 elf64-x86-64
0000000000000000 l df *ABS* 0000000000000000 bss_data_test.c
000000000060103c l O .data 0000000000000004 data_2.2290
0000000000601028 w .data 0000000000000000 data_start
0000000000601038 g O .data 0000000000000004 data_1
0000000000601028 g .data 0000000000000000 __data_start
可以看到,变量所处位置与代码中注释的一致。
我们再运行起来, 看一下地址,确实是data段位于bss段的下方:
./bss_data_test
bss段地址:bss_1 = 0x601050
bss段地址:bss_2 = 0x601044
bss段地址:bss_3 = 0x601048
bss段地址:bss_4 = 0x60104c
data段地址:data_1 = 0x601038
data段地址:data_2 = 0x60103c
再来示例代码
程序1:
int array[30000];
int main() {
return 0;
}
程序2:
int array[30000] = {1, 2, 3, 4, 5, 6};
int main() {
return 0;
}
发现【程序2】编译之后所得的文件比【程序1】的要大得多。
【程序1】编译后文件大小
【程序2】编译后文件大小
于是使用 objdump -t
反汇编查看变量 array
所处存储位置。
程序1:
objdump -t main | grep array
0000000000600e10 l d .init_array 0000000000000000 .init_array
0000000000600e18 l d .fini_array 0000000000000000 .fini_array
0000000000600e18 l O .fini_array 0000000000000000 __do_global_dtors_aux_fini_array_entry
0000000000600e10 l O .init_array 0000000000000000 __frame_dummy_init_array_entry
0000000000600e18 l .init_array 0000000000000000 __init_array_end
0000000000600e10 l .init_array 0000000000000000 __init_array_start
0000000000601060 g O .bss 000000000001d4c0 array
最后一行可以看到,【程序1】的 array 位于.bss段。
我们还可以使用 size
命令,查看每个段大小:
size main
text data bss dec hex filename
1099 544 120032 121675 1db4b main
程序2:
objdump -t main | grep array
0000000000600e10 l d .init_array 0000000000000000 .init_array
0000000000600e18 l d .fini_array 0000000000000000 .fini_array
0000000000600e18 l O .fini_array 0000000000000000 __do_global_dtors_aux_fini_array_entry
0000000000600e10 l O .init_array 0000000000000000 __frame_dummy_init_array_entry
0000000000600e18 l .init_array 0000000000000000 __init_array_end
0000000000600e10 l .init_array 0000000000000000 __init_array_start
0000000000601040 g O .data 000000000001d4c0 array
最后一行可以看到,【程序2】的 array 位于.data段。
使用 size
命令,查看每个段大小,因为text,bss,data段在编译时已经决定了进程将占用多少VM:
size main
text data bss dec hex filename
1099 120560 8 121667 1db43 main
此外,再使用 objdump -s
查看【程序2】中.data段中的数据,可以看到很多很多行的数据:
objdump -s main
main: 文件格式 elf64-x86-64
...省略...
Contents of section .data:
601020 00000000 00000000 00000000 00000000 ................
601030 00000000 00000000 00000000 00000000 ................
601040 01000000 02000000 03000000 04000000 ................
601050 05000000 06000000 00000000 00000000 ................
601060 00000000 00000000 00000000 00000000 ................
601070 00000000 00000000 00000000 00000000 ................
601080 00000000 00000000 00000000 00000000 ................
601090 00000000 00000000 00000000 00000000 ................
6010a0 00000000 00000000 00000000 00000000 ................
6010b0 00000000 00000000 00000000 00000000 ................
6010c0 00000000 00000000 00000000 00000000 ................
6010d0 00000000 00000000 00000000 00000000 ................
6010e0 00000000 00000000 00000000 00000000 ................
6010f0 00000000 00000000 00000000 00000000 ................
601100 00000000 00000000 00000000 00000000 ................
601110 00000000 00000000 00000000 00000000 ................
601120 00000000 00000000 00000000 00000000 ................
601130 00000000 00000000 00000000 00000000 ................
...省略很多很多行...
总结
未初始化的全局变量、静态局部变量,存储在.bss段中,具体体现为一个占位符;
已初始化的全局变量、静态局部变量,存储在.data段中;
此外,非静态局部变量,都在栈中分配空间。
.bss 是不占用.exe文件空间的,其内容由操作系统初始化(清零);
.data 却需要占用,其内容由程序初始化。因此造成了上述情况。
bss 段,不为数据分配空间,只是记录数据所需空间的大小;
bss 段的大小从可执行文件中得到 ,然后链接器得到这个大小的内存块,紧跟在data段后面。
data 段,则为数据分配空间,数据保存在目标文件中;
data 段包含经过初始化的全局变量以及它们的值。