1. 程式人生 > 其它 >《程式設計師的自我修養》第二部分小結

《程式設計師的自我修養》第二部分小結

一個可執行檔案是怎麼來的?從原始碼到可執行檔案經歷了什麼?

第二章

程式原始碼to最終可執行檔案

預編譯:在c語言中,預編譯就是處理那些原始碼中以“#”開始的預編譯命令,如“#include”、“#define”;並刪除所有註釋;新增行號和檔名標識;展開所有巨集定義。.c->.i

編譯:對程式進行一系列詞法分析、語法分析、語義分析及優化,生成對應的彙編程式碼檔案。.i->.s

彙編:根據彙編指令和機器指令的對照表一一翻譯。.s->.o

連結:將一堆檔案連結成一個可執行檔案。.o->.s

程式的編譯

詞法分析:通過掃描器(類似於有限狀態機[1])將程式碼的字元序列分割成一系列的記號。如sum=3+2; 可以表計劃得到以下內容

語素 標記型別
sum 識別符號
= 賦值操作符
3 數字
+ 加法操作符
2 數字
; 語句結束

語法分析:採用上下文無關語法[2]進行分析生成語法樹。

語義分析:分析的是靜態語義[3],給語法樹標識型別。

中間語言生成:將語法樹轉換成中間程式碼。常見有:三地址碼、P-程式碼。

目的碼生成與優化:將中間程式碼轉換成目標機器程式碼,並對目的碼進行優化,如:選擇合適的定址方式、使用位移代替乘法運算、刪除多餘指令等。

第三章

各種目標檔案的格式

ELF檔案的段結構

一個程式本質上都是由BSS段data段text段三個組成。

data段:程式碼段,用來存放已初始化的全域性靜態變數和區域性靜態變數

BSS段Block Start by Symbol,未初始化資料區,通常是指用來存放程式中未初始化的全域性變數和區域性靜態變數的一塊記憶體區域。從邏輯角度出發,這裡的內容也可以放到資料段中,但是因為沒有初始化,預設是0,因此沒有必要在資料段佔用空間,BSS 段實際上至少為未初始化的全域性變數預留位置而已。BSS 段和程式碼段都屬於靜態記憶體分配[4]

程式碼段:存放程式執行程式碼(編譯後的機器碼)的一塊記憶體區域。這部分割槽域大小在程式執行前就已經確定,並且記憶體區域只讀。程式碼斷種也可能有一些只讀常量。

程式分段的好處:

  1. 防止程式指令被有意或無意的篡改;
  2. 提高程式的區域性性,提高快取命中率;
  3. 共享指令。

小結

無論是可執行檔案、目標檔案或庫,他們實際上都是一樣基於段的檔案或是這種檔案的集合。程式的原始碼經過編譯以後,按照程式碼和資料分別存放到相應的段中,編譯器(彙編器)還會將一些輔助性的資訊,諸如符號、重定位資訊等也按照表的方式存放到目標檔案中,而通常情況下,一個表往往就是一個段。

第四章

連結器如何合併各個段到輸出檔案

  1. 空間與地址分配

    獲得各個段的長度、屬性和位置,並整合符號表中的符號定義和符號引用。根據段的長度計算出段合併後的位置和長度。

  2. 符號解析和重定位

    重定位

    編譯器對於一些不明確的地址,會暫時使用假地址代替,連結器會根據重定位表來修正地址。如.text 段中有要被重定向的地方,重定位表會被儲存在.rel.text 中。

    符號解析

    連結器對某個符號的引用進行重定位時,需要確定這個符號的目標地址。這時候就需要查詢有所有輸入目標檔案的符號表組成的全域性符號表。

common 塊

common塊即當需要的空間不一致時,以大的為準。是為了允許不同型別的弱符號[5]存在。因為連結器不支援符號型別,無法判斷各個符號的型別是否一致。

第五章

PE/COFF

和ELF 檔案非常相似,基於段結構的二進位制檔案格式。

PE 是COFF 檔案格式的改進版本,增加了PE 檔案頭、資料目錄等一些結構

最主要的變化有兩個:第一個是檔案最開始的部分不是COFF檔案頭,而是DOS MZ 可執行檔案格式的檔案頭和樁程式碼(DOS MZ File Header and Stub);第二個變化是原來的COFF檔案頭中的“IMAGE_FILE_HEADER”部分擴充套件成了PE檔案檔案頭結構,這個結構包括了原來的“Image Header”及新增的PE擴充套件頭部結構(PE Optional Header)

第一個變化是為了相容DOS 系統。第二個變化是 支援PE檔案的裝在與執行關係。



  1. 有限狀態機:因為滿足了某個“事件”,觸發了一個“動作”,由“狀態A”變成了“狀態B”。 ↩︎

  2. 上下文無關文法:所有產生式的左邊只有一個非終結符。如S -> aSb; S -> ab↩︎

  3. 靜態語義:通常包括宣告和型別的匹配,型別的轉換。動態語義:在執行期間出現的語義相關的問題。 ↩︎

  4. 靜態記憶體分配:在程式開始編譯時完成,不佔用CPU資源;在棧上分配;記憶體塊大小固定。動態記憶體分配:在程式執行中分配;需要指標和引用資料型別;在堆上分配;記憶體按需分配。 ↩︎

  5. 強符號&弱符號:函式和初始化的全域性變數(包括顯式初始化為0)時強符號;未初始化的全域性變數時弱符號。強符號不允許多次定義,但強弱可以共存;強弱共存時,強覆蓋弱;都是弱符號時,選擇佔用空間最大的 ↩︎