一.
在學(xué)習(xí)之前我們先看看ELF文件。
ELF分為三種類型:.o 可重定位文件(relocalble file),可執(zhí)行文件以及共享庫(shared library),三種格式基本上從結(jié)構(gòu)上是一樣的,只是具體到每一個(gè)結(jié)構(gòu)不同。下面我們就從整體上看看這3種格式從文件內(nèi)容上存儲(chǔ)的方式,spec上有張圖是比較經(jīng)典的:如上圖: 其實(shí)從文件存儲(chǔ)的格式來說,上面的兩種view實(shí)際上是一樣的,Segment實(shí)際上就是由section組成的,將相應(yīng)的一些section映射到一起就叫segment了,就是說segment是由0個(gè)或多個(gè)section組成的,實(shí)際上本質(zhì)都是section。在這里我們首先來仔細(xì)了解一下section和segment的概念:section就是相同或者相似信息的集合,比如我們比較熟悉的.text .data .bss section,.text是可執(zhí)行指令的集合,.data是初始化后數(shù)據(jù)的集合,.bss是未初始化數(shù)據(jù)的集合。實(shí)際上我們也可以將一個(gè)程序的所有內(nèi)容都放在一起,就像dos一樣,但是將可執(zhí)行程序分成多個(gè)section是很有好處的,比如說我們可以將.text section放在memory的只讀空間內(nèi),將可變的.data section放在memory的可寫空間內(nèi)。
從可執(zhí)行文件的角度來講,如果一個(gè)數(shù)據(jù)未被初始化那就不需要為其分配空間,所以.data和.bss一個(gè)重要的區(qū)別就是.bss并不占用可執(zhí)行文件的大小,它只是記載需要多少空間來存儲(chǔ)這些未初始化數(shù)據(jù),而不分配實(shí)際的空間。
可以通過命令 $ readelf -l a.out 查看文件的格式和組成。
二.
站在匯編語言的角度,一個(gè)程序分為:數(shù)據(jù)段 -- DS堆棧段 -- SS代碼段 -- CS擴(kuò)展段 -- ES
站在高級(jí)語言的角度,根據(jù)APUE,一個(gè)程序分為如下段:textdata (initialized)bssstackheap
1.一般情況下,一個(gè)可執(zhí)行二進(jìn)制程序(更確切的說,在linux操作系統(tǒng)下為一個(gè)進(jìn)程單元,在UC/OSII中被稱為任務(wù))在存儲(chǔ)(沒有調(diào)入到內(nèi)存運(yùn)行)時(shí)擁有3個(gè)部分,分別是代碼段(text)、數(shù)據(jù)段(data)和BSS段。這3個(gè)部分一起組成了該可執(zhí)行程序的文件。
★★可執(zhí)行二進(jìn)制程序 = 代碼段(text)+數(shù)據(jù)段(data)+BSS段★★
2.而當(dāng)程序被加載到內(nèi)存單元時(shí),則需要另外兩個(gè)域:堆域和棧域。圖1-1所示為可執(zhí)行代碼存儲(chǔ)態(tài)和運(yùn)行態(tài)的結(jié)構(gòu)對照圖。一個(gè)正在運(yùn)行的C程序占用的內(nèi)存區(qū)域分為代碼段、初始化數(shù)據(jù)段、未初始化數(shù)據(jù)段(BSS)、堆、棧5個(gè)部分。
★★正在運(yùn)行的C程序 = 代碼段+初始化數(shù)據(jù)段(data)+未初始化數(shù)據(jù)段(BSS)+堆+棧★★
3.在將應(yīng)用程序加載到內(nèi)存空間執(zhí)行時(shí),操作系統(tǒng)負(fù)責(zé)代碼段、數(shù)據(jù)段和BSS段的加載,并將在內(nèi)存中為這些段分配空間。棧亦由操作系統(tǒng)分配和管理,而不需要程序員顯示地管理;堆段由程序員自己管理,即顯示地申請和釋放空間。
4.動(dòng)態(tài)分配與靜態(tài)分配,二者最大的區(qū)別在于:1. 直到Run-Time的時(shí)候,執(zhí)行動(dòng)態(tài)分配,而在compile-time的時(shí)候,就已經(jīng)決定好了分配多少Text+Data+BSS+Stack。2.通過malloc()動(dòng)態(tài)分配的內(nèi)存,需要程序員手工調(diào)用free()釋放內(nèi)存,否則容易導(dǎo)致內(nèi)存泄露,而靜態(tài)分配的內(nèi)存則在進(jìn)程執(zhí)行結(jié)束后系統(tǒng)釋放(Text, Data), 但Stack段中的數(shù)據(jù)很短暫,函數(shù)退出立即被銷毀。
圖1-1(從可執(zhí)行文件a.out的角度來講,如果一個(gè)數(shù)據(jù)未被初始化那就不需要為其分配空間,所以.data和.bss一個(gè)重要的區(qū)別就是.bss并不占用可執(zhí)行文件的大小,它只是記載需要多少空間來存儲(chǔ)這些未初始化數(shù)據(jù),而不分配實(shí)際的空間)
三.
代碼段 --text(code segment/text segment)text段在內(nèi)存中被映射為只讀,但.data和.bss是可寫的。text段是程序代碼段,在AT91庫中是表示程序段的大小,它是由編譯器在編譯連接時(shí)自動(dòng)計(jì)算的,當(dāng)你在鏈接定位文件中將該符號(hào)放置在代碼段后,那么該符號(hào)表示的值就是代碼段大小,編譯連接時(shí),該符號(hào)所代表的值會(huì)自動(dòng)代入到源程序中。
數(shù)據(jù)段 -- datadata包含靜態(tài)初始化的數(shù)據(jù),所以有初值的全局變量和static變量在data區(qū)。段的起始位置也是由連接定位文件所確定,大小在編譯連接時(shí)自動(dòng)分配,它和你的程序大小沒有關(guān)系,但和程序使用到的全局變量,常量數(shù)量相關(guān)。數(shù)據(jù)段屬于靜態(tài)內(nèi)存分配。
bss段--bssbss是英文Block Started by Symbol的簡稱,通常是指用來存放程序中未初始化的全局變量的一塊內(nèi)存區(qū)域,在程序載入時(shí)由內(nèi)核清0。BSS段屬于靜態(tài)內(nèi)存分配。它的初始值也是由用戶自己定義的連接定位文件所確定,用戶應(yīng)該將它定義在可讀寫的RAM區(qū)內(nèi),源程序中使用malloc分配的內(nèi)存就是這一塊,它不是根據(jù)data大小確定,主要由程序中同時(shí)分配內(nèi)存最大值所確定,不過如果超出了范圍,也就是分配失敗,可以等空間釋放之后再分配。BSS段屬于靜態(tài)內(nèi)存分配。
stack:棧(stack)保存函數(shù)的局部變量(但不包括static聲明的變量, static 意味著 在數(shù)據(jù)段中 存放變量),參數(shù)以及返回值。是一種“后進(jìn)先出”(Last In First Out,LIFO)的數(shù)據(jù)結(jié)構(gòu),這意味著最后放到棧上的數(shù)據(jù),將會(huì)是第一個(gè)從棧上移走的數(shù)據(jù)。對于哪些暫時(shí)存貯的信息,和不需要長時(shí)間保存的信息來說,LIFO這種數(shù)據(jù)結(jié)構(gòu)非常理想。在調(diào)用函數(shù)或過程后,系統(tǒng)通常會(huì)清除棧上保存的局部變量、函數(shù)調(diào)用信息及其它的信息。棧另外一個(gè)重要的特征是,它的地址空間“向下減少”,即當(dāng)棧上保存的數(shù)據(jù)越多,棧的地址就越低。棧(stack)的頂部在可讀寫的RAM區(qū)的最后。
heap:堆(heap)保存函數(shù)內(nèi)部動(dòng)態(tài)分配內(nèi)存,是另外一種用來保存程序信息的數(shù)據(jù)結(jié)構(gòu),更準(zhǔn)確的說是保存程序的動(dòng)態(tài)變量。堆是“先進(jìn)先出”(First In first Out,F(xiàn)IFO)數(shù)據(jù)結(jié)構(gòu)。它只允許在堆的一端插入數(shù)據(jù),在另一端移走數(shù)據(jù)。堆的地址空間“向上增加”,即當(dāng)堆上保存的數(shù)據(jù)越多,堆的地址就越高。
下圖是APUE中的一個(gè)典型C內(nèi)存空間分布圖:
所以可以知道傳入的參數(shù),局部變量,都是在棧頂分布,隨著子函數(shù)的增多而向下增長.函數(shù)的調(diào)用地址(函數(shù)運(yùn)行代碼),全局變量,靜態(tài)變量都是在分配內(nèi)存的低部存在,而malloc分配的堆則存在于這些內(nèi)存之上,并向上生長.
舉例1:
[c] view plain copy#include <stdio h=""> const int g_A = 10; //代碼段 int g_B = 20; //數(shù)據(jù)段 static int g_C = 30; //數(shù)據(jù)段 static int g_D; //BSS段 int g_E; //BSS段 char *p1; //BSS段 void main( ) { int local_A; //棧 int local_B; //棧 static int local_C = 0; //數(shù)據(jù)段 static int local_D; //數(shù)據(jù)段 char *p3 = "123456"; //123456在代碼段,p3在棧上 p1 = (char *)malloc( 10 ); //堆,分配得來得10字節(jié)的區(qū)域在堆區(qū) strcpy( p1, "123456" ); //123456{post.content}放在常量區(qū),編譯器可能會(huì)將它與p3所指向 的"123456"優(yōu)化成一塊 PRintf("hight address/n"); printf("-------------棧--------------/n"); printf( "棧, 局部變量, local_A, addr:0x%08x/n", &local_A ); printf( "棧, 局部變量,(后進(jìn)棧地址相對local_A低) local_B, addr:0x%08x/n", &local_B ); printf("-------------堆--------------/n"); printf( "堆, malloc分配內(nèi)存, p1, addr:0x%08x/n", p1 ); printf("------------BSS段------------/n"); printf( "BSS段, 全局變量, 未初始化 g_E, addr:0x%08x/n", &g_E, g_E ); printf( "BSS段, 靜態(tài)全局變量, 未初始化, g_D, addr:0x%08x/n", &g_D ); printf( "BSS段, 靜態(tài)局部變量, 初始化, local_C, addr:0x%08x/n", &local_C); printf( "BSS段, 靜態(tài)局部變量, 未初始化, local_D, addr:0x%08x/n", &local_D); printf("-----------數(shù)據(jù)段------------/n"); printf( "數(shù)據(jù)段,全局變量, 初始化 g_B, addr:0x%08x/n", &g_B); printf( "數(shù)據(jù)段,靜態(tài)全局變量, 初始化, g_C, addr:0x%08x/n", &g_C); printf("-----------代碼段------------/n"); printf( "代碼段,全局初始化變量, 只讀const, g_A, addr:0x%08x/n/n", &g_A); printf("low address/n"); return; } </stdio>
運(yùn)行結(jié)果:
[c] view plain copyhight address -------------棧-------------- 棧, 局部變量, local_A, addr:0xffa70c1c 棧, 局部變量,(后進(jìn)棧地址相對local_A低) local_B, addr:0xffa70c18 -------------堆-------------- 堆, malloc分配內(nèi)存, p1, addr:0x087fe008 ------------BSS段------------ BSS段, 全局變量, 未初始化 g_E, addr:0x08049a64 BSS段, 靜態(tài)全局變量, 未初始化, g_D, addr:0x08049a5c BSS段, 靜態(tài)局部變量, 初始化, local_C, addr:0x08049a58 BSS段, 靜態(tài)局部變量, 未初始化, local_D, addr:0x08049a54 -----------數(shù)據(jù)段------------ 數(shù)據(jù)段,全局變量, 初始化 g_B, addr:0x08049a44 數(shù)據(jù)段,靜態(tài)全局變量, 初始化, g_C, addr:0x08049a48 -----------代碼段------------ 代碼段,全局初始化變量, 只讀const, g_A, addr:0x08048620 low address
注意:編譯時(shí)需要-g選項(xiàng),這樣才可以看elf信息;readelf -a a.out
查看這個(gè)執(zhí)行文件的elf信息,摘錄部分如下:重點(diǎn)注意其中data段,text段還要有bss段的地址,然后比較這個(gè)地址和上面的運(yùn)行結(jié)果,是否是在elf文件的各個(gè)段的地址之內(nèi)。
[c] view plain copySection Headers: [Nr] Name Type Addr Off Size ES Flg Lk Inf Al [ 0] NULL 00000000 000000 000000 00 0 0 0 [ 1] .interp PROGBITS 08048114 000114 000013 00 A 0 0 1 [ 2] .note.ABI-tag NOTE 08048128 000128 000020 00 A 0 0 4 [ 3] .gnu.hash GNU_HASH 08048148 000148 000020 04 A 4 0 4 [ 4] .dynsym DYNSYM 08048168 000168 000070 10 A 5 1 4 [ 5] .dynstr STRTAB 080481d8 0001d8 000058 00 A 0 0 1 [ 6] .gnu.version VERSYM 08048230 000230 00000e 02 A 4 0 2 [ 7] .gnu.version_r VERNEED 08048240 000240 000020 00 A 5 1 4 [ 8] .rel.dyn REL 08048260 000260 000008 08 A 4 0 4 [ 9] .rel.plt REL 08048268 000268 000028 08 A 4 11 4 [10] .init PROGBITS 08048290 000290 000017 00 AX 0 0 4 [11] .plt PROGBITS 080482a8 0002a8 000060 04 AX 0 0 4 [12] .text PROGBITS 08048310 000310 0002e8 00 AX 0 0 16 [13] .fini PROGBITS 080485f8 0005f8 00001c 00 AX 0 0 4 [14] .rodata PROGBITS 08048614 000614 000326 00 A 0 0 4 [15] .eh_frame PROGBITS 0804893c 00093c 000004 00 A 0 0 4 [16] .ctors PROGBITS 08049940 000940 000008 00 WA 0 0 4 [17] .dtors PROGBITS 08049948 000948 000008 00 WA 0 0 4 [18] .jcr PROGBITS 08049950 000950 000004 00 WA 0 0 4 [19] .dynamic DYNAMIC 08049954 000954 0000c8 08 WA 5 0 4 [20] .got PROGBITS 08049a1c 000a1c 000004 04 WA 0 0 4 [21] .got.plt PROGBITS 08049a20 000a20 000020 04 WA 0 0 4 [22] .data PROGBITS 08049a40 000a40 00000c 00 WA 0 0 4 [23] .bss NOBITS 08049a4c 000a4c 00001c 00 WA 0 0 4 [24] .comment PROGBITS 00000000 000a4c 000114 00 0 0 1 [25] .debug_aranges PROGBITS 00000000 000b60 000020 00 0 0 1 [26] .debug_pubnames PROGBITS 00000000 000b80 00003a 00 0 0 1 [27] .debug_info PROGBITS 00000000 000bba 0001f4 00 0 0 1 [28] .debug_abbrev PROGBITS 00000000 000dae 00006f 00 0 0 1 [29] .debug_line PROGBITS 00000000 000e1d 000058 00 0 0 1 [30] .debug_frame PROGBITS 00000000 000e78 00003c 00 0 0 4 [31] .debug_str PROGBITS 00000000 000eb4 00000d 00 0 0 1 [32] .debug_loc PROGBITS 00000000 000ec1 000043 00 0 0 1 [33] .shstrtab STRTAB 00000000 000f04 000143 00 0 0 1 [34] .symtab SYMTAB 00000000 0015e8 000560 10 35 60 4 [35] .strtab STRTAB 00000000 001b48 0002ad 00 0 0 1 Key to Flags: W (write), A (alloc), X (execute), M (merge), S (strings) I (info), L (link order), G (group), x (unknown) O (extra OS processing required) o (OS specific), p (processor specific)
★★★★注意靜態(tài)變量初始化為零和全局靜態(tài)變量初始化為零的情況,都是存儲(chǔ)在bss段★★★★
從上面的elf文件可以看出,
[23] .bss NOBITS 08049a4c 000a4c 00001c 00 WA 0 0 4
[22] .data PROGBITS 08049a40000a40 00000c 00 WA 0 0 4
[12] .text PROGBITS 08048310000310 0002e8 00 AX 0 0 16
但是在結(jié)果中顯示: BSS段, 靜態(tài)局部變量, 初始化, local_C, addr:0x08049a58
(0x08049a58 大于0x08049a4c 屬于bss段)是初始化的靜態(tài)局部變量但是卻屬于bss段,為什么?
原因是:local_C是局部靜態(tài)變量但是卻初始化為零。這和沒有初始化,默認(rèn)是零的情況一樣,都存儲(chǔ)在bss段,如果初始化為其他的值,那么local_C這個(gè)變量就會(huì)存儲(chǔ)在data段。
四
可執(zhí)行文件大小由什么決定?
可執(zhí)行文件在存儲(chǔ)時(shí)分為代碼段、數(shù)據(jù)段和BSS段三個(gè)部分。
【例一】程序1:int ar[30000];void main(){ ......} 程序2:int ar[300000] = {1, 2, 3, 4, 5, 6 };void main(){ ......} 發(fā)現(xiàn)程序2編譯之后所得的.exe文件比程序1的要大得多。當(dāng)下甚為不解,于是手工編譯了一下,并使用了/FAs編譯選項(xiàng)來查看了一下其各自的.asm,發(fā)現(xiàn)在程序1.asm中ar的定義如下:_BSS SEGMENT ?ar@@3PAHA DD 0493e0H DUP (?) ; ar_BSS ENDS 而在程序2.asm中,ar被定義為:_DATA SEGMENT ?ar@@3PAHA DD 01H ; ar DD 02H DD 03H ORG $+1199988_DATA ENDS 區(qū)別很明顯,一個(gè)位于.bss段,而另一個(gè)位于.data段,兩者的區(qū)別在于:全局的未初始化變量存在于.bss段中,具體體現(xiàn)為一個(gè)占位符;全局的已初始化變量存于.data段中;而函數(shù)內(nèi)的自動(dòng)變量都在棧上分配空間。
.bss是不占用.exe文件空間的,其內(nèi)容由操作系統(tǒng)初始化(清零);而.data卻需要占用,其內(nèi)容由程序初始化,因此造成了上述情況。
以上僅僅做為學(xué)習(xí)只用??!
參考材料:可執(zhí)行文件(ELF)格式的理解http://www.cnblogs.com/xmphoenix/archive/2011/10/23/2221879.htmlC程序內(nèi)存區(qū)域分配(5個(gè)段作用)http://www.cnblogs.com/bigbigtree/archive/2012/11/23/2784137.html新聞熱點(diǎn)
疑難解答
圖片精選
網(wǎng)友關(guān)注