每一個C語言源程序,都將最終經過這一處理而得到相應的目標文件。目標文件中所存放的也就是與源程序等效的目標的機器語言代碼。目標文件由段組成。通常一個目標文件中至少有兩個段(segment):
代碼段:該段中所包含的主要是程序的指令。該段一般是可讀和可執(zhí)行的,但一般卻不可寫。
數(shù)據段:主要存放程序中要用到的各種全局變量或靜態(tài)的數(shù)據。一般數(shù)據段都是可讀,可寫,可執(zhí)行的。
1 目標文件結構
目標文件是源代碼編譯但未鏈接的中間文件(Windows的.obj和Linux的.o),Windows的.obj采用 PE 格式,Linux 采用 ELF 格式,兩種格式均是基于通用目標文件格式(COFF,Common Object File Format)變化而來,所以二者大致相同。
目標文件一般包含編譯后的機器指令代碼、數(shù)據、調試信息,還有鏈接時所需要的一些信息,比如重定位信息和符號表等,而且一般目標文件會將這些不同的信息按照不同的屬性,以“節(jié)(section)”也叫“段(segment)”的形式進行存儲。
#include#include int gInitVar = 1; // .data 加載階段加載 int gUninitVar; // .bss 加載階段加載 const int gConstVar = 2; // .rdata 加載階段加載 // extern可以修飾const用于擴展文件鏈接性,const默認是文件內鏈接的 // static修飾全局變量可以限制其文件鏈接性,其存儲屬性不變 void foo(int i) // .text 加載階段加載 { static int staticLocalInitVar = 3; // .data 加載階段加載 static int staticLocalUninitVar; // .bss 加載階段加載 int stack_localVar = 4; // 棧幀(每個程序運行時會加載1-2M棧空間) const int LocalConstVar = 5; // 棧幀(運行時自動分配) staticLocalUninitVar = LocalConstVar + gConstVar; gUninitVar = staticLocalInitVar + gInitVar; int *dynamic_heapData = (int*)malloc(sizeof(int)*10000000); dynamic_heapData[10000000-1] = 9; // 堆區(qū)(運行時動態(tài)申請) i += stack_localVar + staticLocalUninitVar + gUninitVar; printf("%d ",i+dynamic_heapData[10000000-1]);// .rdata,加載階段加載"%d " free(dynamic_heapData); // 堆內存需要顯式釋放 } // 棧內存在超出作用域后自動釋放 int main() { foo(6); getchar(); return 0; } // 加載階段加載的內存要等到程序結束才釋放
文件的內容分割為不同的區(qū)塊(Setion,又稱區(qū)段,節(jié)等),區(qū)段中包含代碼數(shù)據,各個區(qū)塊按照頁邊界來對齊,區(qū)塊沒有限制大小,是一個連續(xù)的結構。每塊都有他自己在內存中的屬性,比如:這個塊是否可讀可寫,或者只讀等等。
①.text代碼段
代碼段存放程序的機器指令;
② .data已初始化數(shù)據段
初始化數(shù)據段存放已初始化的全局變量與局部靜態(tài)變量;
③ .bss未初始化數(shù)據段
未初始化局部靜態(tài)變量(或初始化為0的局部靜態(tài)變量)放到.bss段,對于未初始化全局變量(或初始化為0的全局變量),不同語言與編譯器的實現(xiàn)有不同的處理,有的只是在.baa段預留一個未定義的全局變量符號,等到最終鏈接成可執(zhí)行文件的時候再在 .bss 段分配空間。編譯器會把未初始化的全局變量標記為一個 COMMON 符號,不為其在 .bss 段分配空間的原因是現(xiàn)在的編譯器和鏈接器支持弱符號機制,即允許同一個弱符號定義在多個目標文件中,因為未初始化的全局變量屬于弱符號,編譯時無法確定符號大小,所以此時無法在 .bss 段為未初始化的全局變量分配空間。
④ .rdata或.rodata只讀數(shù)據段
只讀數(shù)據段存放程序中只讀變量,如const修飾的常量和字符串常量;
單獨設立.rodata段的好處有很多,比如語義上支持了C的const常量,而且操作系統(tǒng)在加載的時候可以將.rodata段的內容映射為只讀區(qū),這樣對于這個段的任何修改都會被判為非法,保證了程序的安全性。
⑤ .symtab符號表段
.symtab段用于存符號表。每個目標文件都有一個相應的符號表(Symbol Table),記錄了目標文件中用到的所有符號。每個符號都有一個對應的值,叫做符號值(Symbol Value),符號值可以是符號所對應的數(shù)據在段中的偏移量,也可以是該符號的對齊屬性。
鏈接過程的本質就是要把多個不同的目標文件之間像拼圖一樣拼起來,相互拼合實際上是目標文件之間對地址的引用,即對函數(shù)和變量的地址的引用。比如目標文件B用到了目標文件A中的函數(shù)foo,那么稱目標文件A定義了函數(shù)foo,目標文件B引用了函數(shù)foo。定義與引用這兩個概念同樣適用于變量。每個函數(shù)和變量都有自己獨一的名字,才能避免鏈接過程中不同變量和函數(shù)之間的混淆。在鏈接中,我們將函數(shù)和變量統(tǒng)稱為符號(Symbol),函數(shù)或變量名就是符號名(Symbol Name)。
符號是鏈接的粘合劑,沒有符號就無法完成鏈接。每一個目標文件都會有一個相應的符號表(Symbol Table),這個表里記錄了目標文件中所用到的所有符號。每個定義的符號有一個對應的值叫做符號值(Symbol Value),對于變量和函數(shù)來說,符號值就是它們的地址。
除了函數(shù)和變量之外,還存在其它幾種不常用到的符號。符號表中的符號可分為全局符號、局部符號、段名、行號等,對于鏈接而言,只關心全局符號。
⑥.strtab字符串表
因為字符串的長度往往是不定的,所以用固定的結構來表示它比較困難。一種很常見的做法是把字符串集中起來存放到一個表,然后使用字符串在表中的偏移來引用字符串(在匯編中使用offset)。
7: char * stringLiteral = "stringLiteral"; 00401038 mov dword ptr [ebp-4],offset string "stringLiteral" (004230c0)
⑦.rela.text代碼段重定位表
重定位表,也叫作重定位段,用于鏈接器在處理目標文件時,重定位代碼段中那些對絕對地址的引用的位置。比如 .text 段中對外部 printf() 函數(shù)的調用。每個要被重定位的地方叫重定位入口(Relocation Entry),OFFSET 表示該入口在所在段中的偏移位置,TYPE 表示重定位入口的類型,VALUE 表示重定位入口的符號名稱。
2 加載與執(zhí)行
項目全部相關文件最終會由鏈接器鏈接到一起形成一個可執(zhí)行文件,Linux 系統(tǒng)中的每個可執(zhí)行文件都運行在一個進程上下文中,有自己的虛擬地址空間。當shell 運行一個程序時,父shell 進程生成一個子進程,它是父進程的一個復制。
子進程通過系統(tǒng)調用啟動加載器。加載器刪除子進程現(xiàn)有的虛擬內存段,并創(chuàng)建一組新的代碼、數(shù)據、堆和棧段。新的棧和堆段被初始化為零。通過將虛擬地址空間中的頁映射到可執(zhí)行文件的頁大小的片(chunk), 新的代碼和數(shù)據段被初始化為可執(zhí)行文件的內容。最后,加載器跳轉到_start地址,它最終會調用應用程序的main 函數(shù)。
一個系統(tǒng)中的進程是與其他進程共享CPU和主存資源的。然而,共享主存會形成一些特殊的挑戰(zhàn)。如果太多的進程需要太多的內存,那么它們中的一些就根本無法運行。當一個程序沒有空間可用時,那就是它運氣不好了。內存還很容易被破壞。如果某個進程不小心寫了另一個進程使用的內存,它就可能以某種完全和程序邏輯無關的令人迷惑的方式失敗。
為了更加有效地管理內存并且少出錯,現(xiàn)代系統(tǒng)提供了一種對主存的抽象概念,叫做虛擬內存(VM)。虛擬內存是硬件異常、硬件地址翻譯、主存、磁盤文件和內核軟件的完美交互,它為每個進程提供了一個大的、一致的和私有的地址空間。通過一個很清晰的機制,虛擬內存提供了三個重要的能力:
1) 它將主存看成是一個存儲在磁盤上的地址空間的高速緩存,在主存中只保存活動區(qū)域,并根據需要在磁盤和主存之間來回傳送數(shù)據,通過這種方式,它高效地使用了主存。
2) 它為每個進程提供了一致的地址空間,從而簡化了內存管理。
3) 它保護了每個進程的地址空間不被其他進程破壞。
虛擬內存是計算機系統(tǒng)最重要的概念之一。它成功的一個主要原因就是因為它是沉默地、自動地工作的,不需要應用程序員的任何干涉。
3 匯編代碼分析
看以下源代碼與匯編代碼的對應,以及數(shù)據(變量)對應的地址值:
1: #include2: #include 3: 4: int gInitVar = 1; // .data 加載階段加載,所以這里無匯編對應 5: int gUninitVar; // .bss 加載階段加載 6: const int gConstVar = 2; // .rdata 加載階段加載 7: 8: void foo(int i) // .text 加載階段加載 9: { 00401020 push ebp 00401021 mov ebp,esp 00401023 sub esp,4Ch 00401026 push ebx 00401027 push esi 00401028 push edi 00401029 lea edi,[ebp-4Ch] 0040102C mov ecx,13h 00401031 mov eax,0CCCCCCCCh 00401036 rep stos dword ptr [edi] 10: static int staticLocalInitVar = 3; // .data 加載階段加載,所以這里無匯編對應 11: static int staticLocalUninitVar; // .bss 加載階段加載 12: int stack_localVar = 4; // 棧幀(每個程序運行時會加載若干M棧空間) 00401038 mov dword ptr [ebp-4],4 // 局部變量保存在棧上,由ebp及其偏移表示 13: const int LocalConstVar = 5; // 棧幀(運行時自動分配) 0040103F mov dword ptr [ebp-8],5 // 局部常量放在棧區(qū) 14: staticLocalUninitVar = LocalConstVar + gConstVar; 00401046 mov dword ptr [gUninitVar+4 (00428e40)],7 15: gUninitVar = staticLocalInitVar + gInitVar; 00401050 mov eax,[global_data+4 (00425a34)] 00401055 add eax,dword ptr [gInitVar (00425a30)] 0040105B mov [gUninitVar (00428e3c)],eax 16: int *dynamic_heapData = (int*)malloc(sizeof(int)*10000000); 00401060 push 2625A00h 00401065 call malloc (00401190) 0040106A add esp,4 0040106D mov dword ptr [ebp-0Ch],eax 17: dynamic_heapData[10000000-1] = 9; // 堆區(qū)(運行時動態(tài)申請) 00401070 mov ecx,dword ptr [ebp-0Ch] 00401073 mov dword ptr [ecx+26259FCh],9 18: i += stack_localVar + staticLocalUninitVar + gUninitVar; 0040107D mov edx,dword ptr [ebp-4] 00401080 add edx,dword ptr [gUninitVar+4 (00428e40)] 00401086 add edx,dword ptr [gUninitVar (00428e3c)] 0040108C mov eax,dword ptr [ebp+8] 0040108F add eax,edx 00401091 mov dword ptr [ebp+8],eax 19: printf("%d ",i+dynamic_heapData[10000000-1]);// .rdata,加載階段加載"%d " 00401094 mov ecx,dword ptr [ebp-0Ch] 00401097 mov edx,dword ptr [ebp+8] 0040109A add edx,dword ptr [ecx+26259FCh] 004010A0 push edx 004010A1 push offset string "xd4xcbxd0xd0xbdxd7xb6xcexa3xbaxb6xafxccxacxc9xeaxc7xeb 004010A6 call printf (00403110) 004010AB add esp,8 20: free(dynamic_heapData); // 堆內存需要顯式釋放 004010AE mov eax,dword ptr [ebp-0Ch] 004010B1 push eax 004010B2 call free (00401c10) 004010B7 add esp,4 21: } // 棧內存在超出作用域后自動釋放
審核編輯:劉清
-
Linux系統(tǒng)
+關注
關注
4文章
595瀏覽量
27451 -
C語言
+關注
關注
180文章
7614瀏覽量
137257 -
虛擬機
+關注
關注
1文章
919瀏覽量
28325 -
類加載器
+關注
關注
0文章
6瀏覽量
937
原文標題:理解C語言程序內存分區(qū)
文章出處:【微信號:c-stm32,微信公眾號:STM32嵌入式開發(fā)】歡迎添加關注!文章轉載請注明出處。
發(fā)布評論請先 登錄
相關推薦
評論