每一個(gè)C語言源程序,都將最終經(jīng)過這一處理而得到相應(yīng)的目標(biāo)文件。目標(biāo)文件中所存放的也就是與源程序等效的目標(biāo)的機(jī)器語言代碼。目標(biāo)文件由段組成。通常一個(gè)目標(biāo)文件中至少有兩個(gè)段(segment):
代碼段:該段中所包含的主要是程序的指令。該段一般是可讀和可執(zhí)行的,但一般卻不可寫。
數(shù)據(jù)段:主要存放程序中要用到的各種全局變量或靜態(tài)的數(shù)據(jù)。一般數(shù)據(jù)段都是可讀,可寫,可執(zhí)行的。
1 目標(biāo)文件結(jié)構(gòu)
目標(biāo)文件是源代碼編譯但未鏈接的中間文件(Windows的.obj和Linux的.o),Windows的.obj采用 PE 格式,Linux 采用 ELF 格式,兩種格式均是基于通用目標(biāo)文件格式(COFF,Common Object File Format)變化而來,所以二者大致相同。
目標(biāo)文件一般包含編譯后的機(jī)器指令代碼、數(shù)據(jù)、調(diào)試信息,還有鏈接時(shí)所需要的一些信息,比如重定位信息和符號(hào)表等,而且一般目標(biāo)文件會(huì)將這些不同的信息按照不同的屬性,以“節(jié)(section)”也叫“段(segment)”的形式進(jìn)行存儲(chǔ)。
#include#include int gInitVar = 1; // .data 加載階段加載 int gUninitVar; // .bss 加載階段加載 const int gConstVar = 2; // .rdata 加載階段加載 // extern可以修飾const用于擴(kuò)展文件鏈接性,const默認(rèn)是文件內(nèi)鏈接的 // static修飾全局變量可以限制其文件鏈接性,其存儲(chǔ)屬性不變 void foo(int i) // .text 加載階段加載 { static int staticLocalInitVar = 3; // .data 加載階段加載 static int staticLocalUninitVar; // .bss 加載階段加載 int stack_localVar = 4; // 棧幀(每個(gè)程序運(yùn)行時(shí)會(huì)加載1-2M??臻g) const int LocalConstVar = 5; // 棧幀(運(yùn)行時(shí)自動(dòng)分配) staticLocalUninitVar = LocalConstVar + gConstVar; gUninitVar = staticLocalInitVar + gInitVar; int *dynamic_heapData = (int*)malloc(sizeof(int)*10000000); dynamic_heapData[10000000-1] = 9; // 堆區(qū)(運(yùn)行時(shí)動(dòng)態(tài)申請(qǐng)) i += stack_localVar + staticLocalUninitVar + gUninitVar; printf("%d ",i+dynamic_heapData[10000000-1]);// .rdata,加載階段加載"%d " free(dynamic_heapData); // 堆內(nèi)存需要顯式釋放 } // 棧內(nèi)存在超出作用域后自動(dòng)釋放 int main() { foo(6); getchar(); return 0; } // 加載階段加載的內(nèi)存要等到程序結(jié)束才釋放
文件的內(nèi)容分割為不同的區(qū)塊(Setion,又稱區(qū)段,節(jié)等),區(qū)段中包含代碼數(shù)據(jù),各個(gè)區(qū)塊按照頁邊界來對(duì)齊,區(qū)塊沒有限制大小,是一個(gè)連續(xù)的結(jié)構(gòu)。每塊都有他自己在內(nèi)存中的屬性,比如:這個(gè)塊是否可讀可寫,或者只讀等等。
①.text代碼段
代碼段存放程序的機(jī)器指令;
② .data已初始化數(shù)據(jù)段
初始化數(shù)據(jù)段存放已初始化的全局變量與局部靜態(tài)變量;
③ .bss未初始化數(shù)據(jù)段
未初始化局部靜態(tài)變量(或初始化為0的局部靜態(tài)變量)放到.bss段,對(duì)于未初始化全局變量(或初始化為0的全局變量),不同語言與編譯器的實(shí)現(xiàn)有不同的處理,有的只是在.baa段預(yù)留一個(gè)未定義的全局變量符號(hào),等到最終鏈接成可執(zhí)行文件的時(shí)候再在 .bss 段分配空間。編譯器會(huì)把未初始化的全局變量標(biāo)記為一個(gè) COMMON 符號(hào),不為其在 .bss 段分配空間的原因是現(xiàn)在的編譯器和鏈接器支持弱符號(hào)機(jī)制,即允許同一個(gè)弱符號(hào)定義在多個(gè)目標(biāo)文件中,因?yàn)槲闯跏蓟娜肿兞繉儆谌醴?hào),編譯時(shí)無法確定符號(hào)大小,所以此時(shí)無法在 .bss 段為未初始化的全局變量分配空間。
④ .rdata或.rodata只讀數(shù)據(jù)段
只讀數(shù)據(jù)段存放程序中只讀變量,如const修飾的常量和字符串常量;
單獨(dú)設(shè)立.rodata段的好處有很多,比如語義上支持了C的const常量,而且操作系統(tǒng)在加載的時(shí)候可以將.rodata段的內(nèi)容映射為只讀區(qū),這樣對(duì)于這個(gè)段的任何修改都會(huì)被判為非法,保證了程序的安全性。
⑤ .symtab符號(hào)表段
.symtab段用于存符號(hào)表。每個(gè)目標(biāo)文件都有一個(gè)相應(yīng)的符號(hào)表(Symbol Table),記錄了目標(biāo)文件中用到的所有符號(hào)。每個(gè)符號(hào)都有一個(gè)對(duì)應(yīng)的值,叫做符號(hào)值(Symbol Value),符號(hào)值可以是符號(hào)所對(duì)應(yīng)的數(shù)據(jù)在段中的偏移量,也可以是該符號(hào)的對(duì)齊屬性。
鏈接過程的本質(zhì)就是要把多個(gè)不同的目標(biāo)文件之間像拼圖一樣拼起來,相互拼合實(shí)際上是目標(biāo)文件之間對(duì)地址的引用,即對(duì)函數(shù)和變量的地址的引用。比如目標(biāo)文件B用到了目標(biāo)文件A中的函數(shù)foo,那么稱目標(biāo)文件A定義了函數(shù)foo,目標(biāo)文件B引用了函數(shù)foo。定義與引用這兩個(gè)概念同樣適用于變量。每個(gè)函數(shù)和變量都有自己獨(dú)一的名字,才能避免鏈接過程中不同變量和函數(shù)之間的混淆。在鏈接中,我們將函數(shù)和變量統(tǒng)稱為符號(hào)(Symbol),函數(shù)或變量名就是符號(hào)名(Symbol Name)。
符號(hào)是鏈接的粘合劑,沒有符號(hào)就無法完成鏈接。每一個(gè)目標(biāo)文件都會(huì)有一個(gè)相應(yīng)的符號(hào)表(Symbol Table),這個(gè)表里記錄了目標(biāo)文件中所用到的所有符號(hào)。每個(gè)定義的符號(hào)有一個(gè)對(duì)應(yīng)的值叫做符號(hào)值(Symbol Value),對(duì)于變量和函數(shù)來說,符號(hào)值就是它們的地址。
除了函數(shù)和變量之外,還存在其它幾種不常用到的符號(hào)。符號(hào)表中的符號(hào)可分為全局符號(hào)、局部符號(hào)、段名、行號(hào)等,對(duì)于鏈接而言,只關(guān)心全局符號(hào)。
⑥.strtab字符串表
因?yàn)樽址拈L度往往是不定的,所以用固定的結(jié)構(gòu)來表示它比較困難。一種很常見的做法是把字符串集中起來存放到一個(gè)表,然后使用字符串在表中的偏移來引用字符串(在匯編中使用offset)。
7: char * stringLiteral = "stringLiteral"; 00401038 mov dword ptr [ebp-4],offset string "stringLiteral" (004230c0)
⑦.rela.text代碼段重定位表
重定位表,也叫作重定位段,用于鏈接器在處理目標(biāo)文件時(shí),重定位代碼段中那些對(duì)絕對(duì)地址的引用的位置。比如 .text 段中對(duì)外部 printf() 函數(shù)的調(diào)用。每個(gè)要被重定位的地方叫重定位入口(Relocation Entry),OFFSET 表示該入口在所在段中的偏移位置,TYPE 表示重定位入口的類型,VALUE 表示重定位入口的符號(hào)名稱。
2 加載與執(zhí)行
項(xiàng)目全部相關(guān)文件最終會(huì)由鏈接器鏈接到一起形成一個(gè)可執(zhí)行文件,Linux 系統(tǒng)中的每個(gè)可執(zhí)行文件都運(yùn)行在一個(gè)進(jìn)程上下文中,有自己的虛擬地址空間。當(dāng)shell 運(yùn)行一個(gè)程序時(shí),父shell 進(jìn)程生成一個(gè)子進(jìn)程,它是父進(jìn)程的一個(gè)復(fù)制。
子進(jìn)程通過系統(tǒng)調(diào)用啟動(dòng)加載器。加載器刪除子進(jìn)程現(xiàn)有的虛擬內(nèi)存段,并創(chuàng)建一組新的代碼、數(shù)據(jù)、堆和棧段。新的棧和堆段被初始化為零。通過將虛擬地址空間中的頁映射到可執(zhí)行文件的頁大小的片(chunk), 新的代碼和數(shù)據(jù)段被初始化為可執(zhí)行文件的內(nèi)容。最后,加載器跳轉(zhuǎn)到_start地址,它最終會(huì)調(diào)用應(yīng)用程序的main 函數(shù)。

一個(gè)系統(tǒng)中的進(jìn)程是與其他進(jìn)程共享CPU和主存資源的。然而,共享主存會(huì)形成一些特殊的挑戰(zhàn)。如果太多的進(jìn)程需要太多的內(nèi)存,那么它們中的一些就根本無法運(yùn)行。當(dāng)一個(gè)程序沒有空間可用時(shí),那就是它運(yùn)氣不好了。內(nèi)存還很容易被破壞。如果某個(gè)進(jìn)程不小心寫了另一個(gè)進(jìn)程使用的內(nèi)存,它就可能以某種完全和程序邏輯無關(guān)的令人迷惑的方式失敗。
為了更加有效地管理內(nèi)存并且少出錯(cuò),現(xiàn)代系統(tǒng)提供了一種對(duì)主存的抽象概念,叫做虛擬內(nèi)存(VM)。虛擬內(nèi)存是硬件異常、硬件地址翻譯、主存、磁盤文件和內(nèi)核軟件的完美交互,它為每個(gè)進(jìn)程提供了一個(gè)大的、一致的和私有的地址空間。通過一個(gè)很清晰的機(jī)制,虛擬內(nèi)存提供了三個(gè)重要的能力:
1) 它將主存看成是一個(gè)存儲(chǔ)在磁盤上的地址空間的高速緩存,在主存中只保存活動(dòng)區(qū)域,并根據(jù)需要在磁盤和主存之間來回傳送數(shù)據(jù),通過這種方式,它高效地使用了主存。
2) 它為每個(gè)進(jìn)程提供了一致的地址空間,從而簡化了內(nèi)存管理。
3) 它保護(hù)了每個(gè)進(jìn)程的地址空間不被其他進(jìn)程破壞。
虛擬內(nèi)存是計(jì)算機(jī)系統(tǒng)最重要的概念之一。它成功的一個(gè)主要原因就是因?yàn)樗浅聊?、自?dòng)地工作的,不需要應(yīng)用程序員的任何干涉。

3 匯編代碼分析
看以下源代碼與匯編代碼的對(duì)應(yīng),以及數(shù)據(jù)(變量)對(duì)應(yīng)的地址值:
1: #include2: #include 3: 4: int gInitVar = 1; // .data 加載階段加載,所以這里無匯編對(duì)應(yīng) 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 加載階段加載,所以這里無匯編對(duì)應(yīng) 11: static int staticLocalUninitVar; // .bss 加載階段加載 12: int stack_localVar = 4; // 棧幀(每個(gè)程序運(yùn)行時(shí)會(huì)加載若干M??臻g) 00401038 mov dword ptr [ebp-4],4 // 局部變量保存在棧上,由ebp及其偏移表示 13: const int LocalConstVar = 5; // 棧幀(運(yùn)行時(shí)自動(dòng)分配) 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ū)(運(yùn)行時(shí)動(dòng)態(tài)申請(qǐng)) 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); // 堆內(nèi)存需要顯式釋放 004010AE mov eax,dword ptr [ebp-0Ch] 004010B1 push eax 004010B2 call free (00401c10) 004010B7 add esp,4 21: } // 棧內(nèi)存在超出作用域后自動(dòng)釋放
審核編輯:劉清
-
Linux系統(tǒng)
+關(guān)注
關(guān)注
4文章
616瀏覽量
30140 -
C語言
+關(guān)注
關(guān)注
183文章
7646瀏覽量
146120 -
虛擬機(jī)
+關(guān)注
關(guān)注
1文章
975瀏覽量
30688 -
類加載器
+關(guān)注
關(guān)注
0文章
6瀏覽量
1041
原文標(biāo)題:理解C語言程序內(nèi)存分區(qū)
文章出處:【微信號(hào):c-stm32,微信公眾號(hào):STM32嵌入式開發(fā)】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
一文詳解C語言內(nèi)存管理
C語言程序設(shè)計(jì)中動(dòng)態(tài)內(nèi)存分配如何實(shí)現(xiàn)
c語言指針詳解
單片機(jī)C語言程序與數(shù)據(jù)存儲(chǔ)的相關(guān)資料分享
存儲(chǔ)器的分區(qū)內(nèi)存管理與分區(qū)存儲(chǔ)管理
使用單片機(jī)實(shí)現(xiàn)62256擴(kuò)展內(nèi)存的C語言程序免費(fèi)下載
單片機(jī)C語言程序與數(shù)據(jù)存儲(chǔ)
C語言程序編譯后內(nèi)存地址的分配
C語言內(nèi)存問題如何解決
C語言內(nèi)存泄漏問題原理
快速搞懂C語言程序內(nèi)存分區(qū)!
詳解C語言程序內(nèi)存分區(qū)
評(píng)論