美文网首页arm
深入理解BSS段与data段的区别

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

作者: JamFF | 来源:发表于2021-03-21 16:03 被阅读0次

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


    参考资料:(深入理解计算机系统) bss段,data段、text段、堆(heap)和栈(stack)

    相关文章

      网友评论

        本文标题:深入理解BSS段与data段的区别

        本文链接:https://www.haomeiwen.com/subject/dbmdcltx.html