我們在學習C程式開發時經常會遇到一些概念:代碼段、資料段、BSS段(Block Started by Symbol) 、堆(heap)和棧(stack)。先看一張教材上的示意圖(來源,《UNIX環境進階程式設計》一書),顯示了程序位址空間中典型的存儲區域配置設定情況。

從圖中可以看出:
從低位址到高位址分别為:代碼段、(初始化)資料段、(未初始化)資料段(BSS)、堆、棧、指令行參數和環境變量
堆向高記憶體位址生長
棧向低記憶體位址生長
還經常看到下面這個圖(來源,不詳):
先看一段程式。
下面是輸出結果。
先仔細分析一下上面的輸出結果,看看能得出什麼結論。貌似很難分析出來什麼結果。好了我們繼續往下看吧。
接下來,通過檢視proc檔案系統下的檔案,看一下這個程序的真實記憶體配置設定情況。(我們需要在程式結束前加一個死循環,不讓程序結束,以便我們進一步分析)。
在return 0前,增加 while(1); 語句
重新編譯後,運作程式,程式将進入死循環。
使用ps指令檢視一下程序的pid
#ps -aux | grep a.out
檢視/proc/2699/maps檔案,這個檔案顯示了程序在記憶體空間中各個區域的配置設定情況。
#cat /proc/2699/maps
上面紅顔色标出的幾個區間是我們感興趣的區間:
08048000-08049000 r-xp 貌似是代碼段
08049000-0804a000 r--p 暫時不清楚,看不出來
0804a000-0804b000 rw-p 貌似為資料段
08a7e000-08a9f000 rw-p 堆
bff73000-bff88000 rw-p 棧
我們把這些資料與剛才的程式運作結果進行比較,看看有什麼結論。
&global_init_a=0x804a018 全局初始化:資料段 global_init_a=1
&global_uninit_a=0x804a04c 全局未初始化:資料段 global_uninit_a=0
&static_global_init_a=0x804a01c 全局靜态初始化:資料段 static_global_init_a=1
&static_global_uninit_a=0x804a038 全局靜态未初始化:資料段 static_global_uninit_a=0
&const_global_a=0x80487c0 全局隻讀變量: 代碼段 const_global_a=1
&global_init_b=0x804a020 全局初始化:資料段 global_init_b=1
&global_uninit_b=0x804a048 全局未初始化:資料段 global_uninit_b=0
&static_global_init_b=0x804a024 全局靜态初始化:資料段 static_global_init_b=1
&static_global_uninit_b=0x804a03c 全局靜态未初始化:資料段 static_global_uninit_b=0
&const_global_b=0x80487c4 全局隻讀變量: 代碼段 const_global_b=1
&local_init_a=0xbff8600c 局部初始化:棧 local_init_a=1
&local_uninit_a=0xbff86008 局部未初始化:棧 local_uninit_a=134514459
&static_local_init_a=0x804a028 局部靜态初始化:資料段 static_local_init_a=1
&static_local_uninit_a=0x804a040 局部靜态未初始化:資料段 static_local_uninit_a=0
&const_local_a=0xbff86004 局部隻讀變量:棧 const_local_a=1
&local_init_b=0xbff86000 局部初始化:棧 local_init_b=1
&local_uninit_b=0xbff85ffc 局部未初始化:棧 local_uninit_b=-1074241512
&static_local_init_b=0x804a02c 局部靜态初始化:資料段 static_local_init_b=1
&static_local_uninit_b=0x804a044 局部靜态未初始化:資料段 static_local_uninit_b=0
&const_local_b=0xbff85ff8 局部隻讀變量:棧 const_local_b=1
p_chars=0x80487c8 字元串常量:代碼段 p_chars=abcdef
malloc_p_a=0x8a7e008 malloc動态配置設定:堆 *malloc_p_a=0
通過以上分析我們暫時可以得到的結論如下,在程序的位址空間中:
資料段中存放:全局變量(初始化以及未初始化的)、靜态變量(全局的和局部的、初始化的以及未初始化的)
代碼段中存放:全局隻讀變量(const)、字元串常量
堆中存放:動态配置設定的區域
棧中存放:局部變量(初始化以及未初始化的,但不包含靜态變量)、局部隻讀變量(const)
這裡我們沒有發現BSS段,但是我們将未初始化的資料按照位址進行排序看一下,可以發現一個規律。
&static_global_init_a=0x804a01c 全局靜态初始化:資料段 static_global_init_a=1
&global_init_b=0x804a020 全局初始化:資料段 global_init_b=1
&static_global_init_b=0x804a024 全局靜态初始化:資料段 static_global_init_b=1
&static_local_init_a=0x804a028 局部靜态初始化:資料段 static_local_init_a=1
&static_local_init_b=0x804a02c 局部靜态初始化:資料段 static_local_init_b=1
&static_local_uninit_a=0x804a040 局部靜态未初始化:資料段 static_local_uninit_a=0
&static_local_uninit_b=0x804a044 局部靜态未初始化:資料段 static_local_uninit_b=0
&global_uninit_b=0x804a048 全局未初始化:資料段 global_uninit_b=0
這裡可以發現,初始化的和未初始化的資料好像是分開存放的,是以我們可以猜測BSS段是存在的,隻不過資料段是分為初始化和未初始化(即BSS段)的兩部分,他們在加載到程序位址空間時是合并為資料段了,在程序位址空間中沒有單獨分為一個區域。
還有一個問題,靜态資料與非靜态資料是否是分開存放的呢?請讀者自行分析一下。
接下來我們從程式的角度看一下,這寫存儲區域是如何配置設定的。首先我們先介紹一下ELF檔案格式。
ELF(Executable and Linkable Format )檔案格式是一個開放标準,各種UNIX系統的可執行檔案都采用ELF格式,它有三種不同的類型:
–可重定位的目标檔案(Relocatable,或者Object File)
–可執行檔案(Executable)
–共享庫(Shared Object,或者Shared Library)
下圖為ELF檔案的結構示意圖(來源,不詳):
一個程式編譯生成目标代碼檔案(ELF檔案)的過程如下,此圖引自《程式員的自我修養》一書的一個圖:
可以通過readelf指令檢視EFL檔案的相關資訊,例如 readelf -a a.out ,我們隻關心各個段的配置設定情況,是以我們使用以下指令:
# readelf -S a.out
将這裡的記憶體布局與之前看到的程式的運作結果進行分析:
&global_uninit_a=0x804a04c 全局未初始化:BSS段 global_uninit_a=0
&static_global_uninit_a=0x804a038 全局靜态未初始化:BSS段 static_global_uninit_a=0
&const_global_a=0x80487c0 全局隻讀變量: 隻讀資料段 const_global_a=1
&global_uninit_b=0x804a048 全局未初始化:BSS段 global_uninit_b=0
&static_global_uninit_b=0x804a03c 全局靜态未初始化:BSS段 static_global_uninit_b=0
&const_global_b=0x80487c4 全局隻讀變量: 隻讀資料段 const_global_b=1
&static_local_uninit_a=0x804a040 局部靜态未初始化:BSS段 static_local_uninit_a=0
&static_local_init_b=0x804a02c 局部靜态初始化:資料段 static_local_init_b=1
&static_local_uninit_b=0x804a044 局部靜态未初始化:BSS段 static_local_uninit_b=0
p_chars=0x80487c8 字元串常量:隻讀資料段 p_chars=abcdef
ELF 檔案一般包含以下幾個段 :
.text section:主要是編譯後的源碼指令,是隻讀字段。
.data section :初始化後的非const的全局變量、局部static變量。
.bss:未初始化後的非const全局變量、局部static變量。
.rodata字段 是存放隻讀資料
分析到這以後,我們在和之前分析的結果對比一下,會發現确實存在BSS段,位址為0804a030 ,大小為0x20,之前我們的程式中未初始化的的确存放在這個位址區間中了,隻不過執行exec系統調用時,将這部分的資料初始化為0後,放到了程序位址空間的資料段中了,在程序位址空間中就沒有必要存在BSS段了,是以都稱做資料段。同理,.rodata字段也是與text段放在一起了。
在ELF檔案中,找不到局部非靜态變量和動态配置設定的内容。
以上有很多地方的分析,我在網上基本找不到很明确的結論,很多教材上也沒有描述,隻是我通過程式分析得出的結論,如有不妥之處,請指出,歡迎交流。