深入理解BSS段与data段的区别

在解释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 段包含经过初始化的全局变量以及它们的值。

  • 4
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值