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


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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,772评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,458评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,610评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,640评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,657评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,590评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,962评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,631评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,870评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,611评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,704评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,386评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,969评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,944评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,179评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,742评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,440评论 2 342

推荐阅读更多精彩内容