在解釋bss段與data段區(qū)別前案淋,先來(lái)看下他們定義妓湘,以及內(nèi)存中的位置员咽。
虛擬地址空間
在32位x86的Linux系統(tǒng)中妆兑,虛擬地址空間布局如下圖所示:
- bss段(bss segment):bss是Block Started by Symbol的簡(jiǎn)稱(chēng),用來(lái)存放程序中未初始化的全局變量的內(nèi)存區(qū)域苏携,屬于靜態(tài)內(nèi)存分配订框。
- data段(data segment):用來(lái)存放程序中已初始化的全局變量的內(nèi)存區(qū)域,屬于靜態(tài)內(nèi)存分配兜叨。
- text段(text segment):用來(lái)存放程序執(zhí)行代碼的內(nèi)存區(qū)域。這部分區(qū)域的大小在程序運(yùn)行前就已經(jīng)確定衩侥,并且內(nèi)存區(qū)域通常屬于只讀(某些架構(gòu)也允許代碼段為可寫(xiě)国旷,即允許修改程序)。也有可能包含一些只讀的常數(shù)變量茫死,例如字符串常量等跪但。
- 堆(heap):用于存放進(jìn)程運(yùn)行中被動(dòng)態(tài)分配的內(nèi)存段,它的大小并不固定峦萎,可動(dòng)態(tài)擴(kuò)張或縮減屡久。
- 棧(stack):用戶(hù)存放程序臨時(shí)創(chuàng)建的局部變量,也就是說(shuō)我們函數(shù)括弧“{}”中定義的變量(但不包括static聲明的變量爱榔,static意味著在data段中存放變量)被环。除此以外,在函數(shù)被調(diào)用時(shí)详幽,其參數(shù)也會(huì)被壓入發(fā)起調(diào)用的進(jìn)程棧中筛欢,并且待到調(diào)用結(jié)束后,函數(shù)的返回值也會(huì)被存放回棧中唇聘。
上面只是簡(jiǎn)單介紹了各個(gè)內(nèi)存段版姑,如果想詳細(xì)了解,可以看這篇文章《深度解析內(nèi)存中的程序》迟郎。
bss段與data段的區(qū)別
在初始化時(shí) bss 段部分將會(huì)清零剥险。bss 段屬于靜態(tài)內(nèi)存分配,即程序一開(kāi)始就將其清零了宪肖。
比如表制,在C語(yǔ)言之類(lèi)的程序編譯完成之后健爬,已初始化的全局變量保存在.data 段中,未初始化的全局變量保存在.bss 段中夫凸。
- text 和 data 段都在可執(zhí)行文件中浑劳,由系統(tǒng)從可執(zhí)行文件中加載;
- 而 bss 段不在可執(zhí)行文件中夭拌,由系統(tǒng)初始化魔熏。
上面這些理論可能有些抽象,下面我們通過(guò)示例代碼來(lái)對(duì)比兩者區(qū)別鸽扁。
示例代碼
#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;// 未初始化的靜態(tài)局部變量躲雅,bss段
static int bss_4 = 0;// 初始化為0靜態(tài)局部變量,bss段
static int data_2 = 1;// 初始化非0靜態(tài)局部變量骡和,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
反匯編查看變量的存儲(chǔ)位置相赁。
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
可以看到,變量所處位置與代碼中注釋的一致慰于。
我們?cè)龠\(yùn)行起來(lái)钮科, 看一下地址,確實(shí)是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
再來(lái)示例代碼
程序1:
int array[30000];
int main() {
return 0;
}
程序2:
int array[30000] = {1, 2, 3, 4, 5, 6};
int main() {
return 0;
}
發(fā)現(xiàn)【程序2】編譯之后所得的文件比【程序1】的要大得多婆赠。
于是使用 objdump -t
反匯編查看變量 array
所處存儲(chǔ)位置绵脯。
程序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
命令蛆挫,查看每個(gè)段大小:
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
命令,查看每個(gè)段大小拭嫁,因?yàn)閠ext畜挨,bss,data段在編譯時(shí)已經(jīng)決定了進(jìn)程將占用多少VM:
size main
text data bss dec hex filename
1099 120560 8 121667 1db43 main
此外噩凹,再使用 objdump -s
查看【程序2】中.data段中的數(shù)據(jù)巴元,可以看到很多很多行的數(shù)據(jù):
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 ................
...省略很多很多行...
總結(jié)
未初始化的全局變量、靜態(tài)局部變量驮宴,存儲(chǔ)在.bss段中逮刨,具體體現(xiàn)為一個(gè)占位符;
已初始化的全局變量、靜態(tài)局部變量修己,存儲(chǔ)在.data段中恢总;
此外,非靜態(tài)局部變量睬愤,都在棧中分配空間片仿。
.bss 是不占用.exe文件空間的,其內(nèi)容由操作系統(tǒng)初始化(清零)尤辱;
.data 卻需要占用砂豌,其內(nèi)容由程序初始化。因此造成了上述情況光督。
bss 段阳距,不為數(shù)據(jù)分配空間,只是記錄數(shù)據(jù)所需空間的大薪峤琛筐摘;
bss 段的大小從可執(zhí)行文件中得到 ,然后鏈接器得到這個(gè)大小的內(nèi)存塊船老,緊跟在data段后面咖熟。
data 段,則為數(shù)據(jù)分配空間柳畔,數(shù)據(jù)保存在目標(biāo)文件中球恤;
data 段包含經(jīng)過(guò)初始化的全局變量以及它們的值。
參考資料:(深入理解計(jì)算機(jī)系統(tǒng)) bss段荸镊,data段、text段堪置、堆(heap)和棧(stack)