程式代碼到可執行程式編譯鏈接程序
預編譯
以c++/c 語言為例,預編譯階段的作業有以下幾點:
- 處理所有#define 及條件預編譯指令(如 #if,#ifdef.....),并展開所有宏定義,
- 洗掉所有注釋("//" ,"/**/"),
- 處理 "#include",將被包含檔案插入該預編譯指令位置,(整過程序遞回進行,因為被包含檔案也可能包含其他檔案)
- 添加行號與檔案標識,(用于除錯時產生的編譯錯誤及報錯等資訊)
預編譯程序相當于如下命令:
gcc -E hello.c -o hello.i (-E 表示只進行預編譯)
或者
cpp hello.c > hello.i
編譯
編譯程序可以分為如下步驟:

-
掃描
-
詞法分析
? 運用一種類似于有限狀態機的演算法,將源代碼的字符序列分割為一系列記號(關鍵字、識別符號、字面常量、特殊符號等),【一個名叫lex的程式可以完成這項任務】
-
語法分析
? 對由掃描器產生的記號進行語法分析,進而產生語法樹,(采用背景關系無關的語法分析手段)【同樣一個叫做yacc的工具也可完成這項任務】
-
語意分析
? 包括靜態語意(如宣告和型別的匹配、型別的轉化等)和動態語意(運行階段才能確定),
-
源代碼優化【這階段也包括中間代碼(例如llvm 中的 IR)的生成】
? 由于直接在語法樹上作優化難度較大,源代碼優化器通常將語法樹轉化為中間代碼,再進行優化,
-
目標代碼生成和目標代碼優化
? 代碼生成器將中間代碼轉化成目標機器代碼,
? 接著目標代碼優化器對上述目標代碼進行優化,(如選擇合適的尋址方式,洗掉多余指令等)
編譯程序相當于如下命令:
gcc -S hello.i -o hello.s (.s 是匯編輸出檔案的后綴)
或者
gcc -S hello.c -o hello.s (預編譯和編譯合并了)
匯編
匯編器將匯編代碼轉變為機器可以執行的指令,(生成可重定位檔案 .o)
編譯程序相當于如下命令:
as hello.s -o hello.o
或者
gcc -c hello.s -o hello.o
或者
gcc -c hello.c -hello.o (上面三個程序一步完成)
鏈接
對于一個復雜的軟體,將每個源代碼模塊獨立地翻譯,然后組裝,這個組裝模塊的程序就是鏈接,(主要包括地址和空間分配、符號決議、重定位等步驟)
最基本的靜態鏈接程序:每個模塊的源代碼檔案(如.c)檔案經過編譯器編譯成可重定位檔案(Object File,擴展名為.o或.obj),可重定位檔案和庫一起鏈接形成最終可執行檔案(.out),

鏈接程序相當于如下命令:
gcc hello.o -o hello.out
以如下代碼為例:
#include<stdio.h>
int main()
{
printf("hello world");
return 0;
}
| 預編譯(hello.i) | 編譯(hello.s) |
|---|---|
![]() |
![]() |
| 匯編(hello.o) | 鏈接(hello.out) |
![]() |
![]() |
可重定位檔案 [.o 或 .obj]
可重定位檔案的格式
目前PC平臺流行的可執行檔案格式(Executable)主要是:
PE(Windows)和 ELF(Linux),【兩者都發源自 COFF 可執行檔案格式】
另外的如ios 是 Mach-O格式,android 是dex格式,
而可重定位檔案是源代碼編譯后但未進行鏈接的中間檔案,(Windows 下的.obj 和 Linux 下的.o),
因此,可重定位檔案和可執行檔案的內容和結構是很相似的,(可以廣義的將二者看作一種型別的檔案)
同時元件(Windows 下的.dll 和 Linux 下的.so)和 靜態鏈接庫(Windows 下的.lib 和 Linux 下的.a)檔案都可按照可執行檔案格式存盤,
【小技巧: Linux 下可使用file命令查看相應的檔案格式】
程式的指令和資料分開存放的好處:
- 程式裝載后,資料和指令分別映射到兩個虛存區域,資料區域對行程而言是可讀寫的,指令區域對于行程而言是只讀的,這樣可以防止程式指令被有意或者無意地更改,
- 利于提高程式的區域性,(提高快取的命中率)
- 當系統中運行著多個該程式副本時,記憶體中只需要保存一份該程式的指令部分,(最重要的原因)
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/545679.html
標籤:C
上一篇:C++時間對秒數的運算
下一篇:Hello,Golang




