文章目錄
- 1. C 語言編譯原理和詳細過程
- 1.1 預處理階段
- 1.2 編譯階段
- 1.3 匯編階段
- 1.4 鏈接階段
- 2. 疑問點解析
- 2.1 三地址碼是什么?有什么作用
- 2.2 符號表是什么?有何作用
- 2.3 重定位的含義與作用
- 2.3 符號表和重定位在整個編譯過程中的作用
- 2.4 動態鏈接庫.so和靜態鏈接庫.a
- 2.5 不要混淆.o文件和.so文件
- 3. 知識補充
1. C 語言編譯原理和詳細過程
編譯是將源代碼轉換為計算機可執行的二進制文件的過程,整個過程主要分為四個階段: 預處理; 編譯; 匯編; 鏈接。
1.1 預處理階段
工具:預處理器
輸入:.c 源文件
輸出:.i 預處理后的文件
預處理階段做的主要工作:
- 宏展開:將所有
#define
定義的宏進行文本替換 - 頭文件包含:遞歸展開
#include
指令,將頭文件內容插入源代碼 - 條件編譯:處理條件編譯指令(如
#ifdef
等命令),根據條件保留或刪除代碼塊(如調試代碼) - 刪除注釋
1.2 編譯階段
工具:編譯器
輸入:.i文件
輸出:.s匯編代碼文件
編譯階段做的主要工作:
- 詞法分析:將代碼拆分為token(如標識符,關鍵字,運算符)
- 語法分析:構建抽象語法樹(AST),檢查語法是否符合C標準(如檢查括號是否匹配,語句是否合法)
- 語義分析:檢查類型匹配,變量聲明,作用域規則
- 中間代碼生成:生成與平臺無關的中間表示(如三地址碼)
- 代碼優化:對中間代碼進行優化。如刪除冗余代碼、常量折疊等
- 目標代碼生成:將優化后的中間代碼轉換為目標平臺的匯編代碼
1.3 匯編階段
工具:匯編器
輸入:.s匯編文件
輸出:.o目標文件,即二進制機器碼
匯編階段做的主要工作:
- 指令轉換:將匯編代碼逐行轉換為機器碼
- 生成目標文件:生成包含機器碼、符號表、重定位信息的**.o文件**
1.4 鏈接階段
工具:鏈接器
輸入:多個.o目標文件+靜態庫.a文件
輸出:可執行文件
鏈接階段做的主要工作:
- 符號解析:解決跨文件的函數或變量引用。如main.o中調用printf函數,則需找到該函數在libc.a中的定義
- 重定位:合并所有目標文件的代碼段、數據段,分配最終內存地址;修正符號表中的地址偏移量
- 庫文件處理:
- 靜態鏈接:將靜態庫.a代碼直接復制到可執行文件中
- 動態鏈接:記錄動態庫.so的路徑,運行時加載 - 生成可執行文件:生成符合操作系統格式的可執行文件
注:可執行文件分為 代碼段(.text)、數據段(.data)、未初始化數據段(.bss) 等
經過這一系列過程,c源代碼最終變成了操作系統可直接執行的二進制文件 運行時由加載器將其讀入內存并執行。
2. 疑問點解析
2.1 三地址碼是什么?有什么作用
三地址碼(Three-Address Code,TAC)是編譯器中常用的一種中間表示(Intermediate Representation, IR)形式,它將復雜的表達式和語句拆解為一系列簡單的指令,每條指令最多包含三個操作數。它的核心目標是簡化代碼優化和目標代碼生成的過程,同時保持與機器無關的特性。
每條指令僅包含一個操作(如賦值,運算,跳轉等),最多涉及三個操作數,兩個輸入一個輸出。常見的通用格式如下:
result = operand1 op operand2
2.2 符號表是什么?有何作用
符號表是編譯器/匯編器生成的一種數據結構,記錄了程序中所有符號的信息:
- 符號名稱:如函數名、全局變量名稱;
- 符號類型:函數、變量、靜態/全局作用域等;
- 符號地址:在目標文件中的相對地址或內存地址。
符號分類:
- 全局符號:可被其他文件訪問的符號,如extern變量、非static函數;
- 局部符號:僅在本文件內可見的符號,如static函數或變量;
- 外部符號:在本文件中使用但未定義的符號,如調用了其他文件中的函數。
在鏈接階段,鏈接器通過符號表解析不同目標文件之間的符號引用,如函數調用、變量訪問等;符號表包含了符號的地址和類型,支持調試器(如gdb)定位代碼和變量(比如有時候調試的時候會導入符號表);動態鏈接庫.so需要依賴符號表在運行時綁定函數地址。
2.3 重定位的含義與作用
重定位時鏈接器在合并多個目標文件時,修正符號引用地址的過程。目標文件.o中的代碼和數據地址是臨時地址(基于偏移量的),鏈接器需要將其調整為最終可執行文件的絕對地址。
重定位表:每個目標文件都有一個重定位表,記錄了需要修正的位置及其規則:
- 需要修正的偏移量:在目標文件中的位置;
- 符號名稱:需要修正為哪個符號的地址;
- 重定位類型:如何計算最終地址,如相對地址或絕對地址。
重定位的作用:
- 合并多目標文件:將分散在多個.o文件中的代碼和數據分配到統一的內存布局中;
- 解析外部依賴:將未定義的符號綁定到庫中的實際地址;
- 生成可執行文件:確保程序運行時,所有指令和數據的地址正確。
2.3 符號表和重定位在整個編譯過程中的作用
編譯階段會生成符號表并記錄重定位信息:
- 生成符號表:編譯器為每個.c文件生成.o目標文件,包含符號表和代碼;
- 記錄重定位信息:編譯器標記所有需要重定位的位置,如外部函數調用等。
鏈接階段會進行符號解析以及地址分配與重定位:
- 符號解析:鏈接器會檢查所有目標文件的符號表,確保每個符號有且僅有一個定義;
- 地址分配與重定位:鏈接器為所有符號分配最終地址,并修正代碼中的引用。
即符號表是程序符號的地址簿,記錄了符號的定義和引用;重定位是鏈接器的修正工具,確保程序可以正確訪問所有符號。
2.4 動態鏈接庫.so和靜態鏈接庫.a
動態鏈接庫.so文件,是在程序運行時被加載的,多個程序可共享同一個庫文件,節省內存和磁盤空間。
靜態鏈接庫.a文件,是在編譯時被整合到可執行文件中,生成獨立的可執行文件,不需要外部依賴。
靜態鏈接庫和動態鏈接庫的主要區別:
特性 | 靜態庫(.a) | 動態庫(.so) |
---|---|---|
鏈接方式 | 編譯時直接嵌入到可執行文件中 | 程序運行時動態加載 |
文件體積 | 可執行文件體積較大(包含庫代碼) | 可執行文件體積較小(僅存引用) |
運行時依賴 | 無需外部庫文件 | 必須存在對應的.so 文件 |
內存占用 | 每個進程獨立加載庫代碼,內存冗余 | 多個進程共享同一份庫代碼,內存節省 |
更新維護 | 需重新編譯整個程序 | 僅替換.so 文件即可更新庫功能 |
加載速度 | 啟動快(代碼已嵌入) | 啟動稍慢(需加載動態庫) |
兼容性風險 | 無版本沖突問題 | 需保證.so 版本與程序兼容 |
常見使用場景 | 嵌入式系統、獨立工具、無依賴部署 | 通用系統庫(如libc )、多進程共享場景 |
示例:
-
靜態鏈接:
gcc main.c -o program -L/path/to/libs -lstaticlib -static
-static選項表明強制靜態鏈接所有庫,包括庫系統如libc
這樣生成的program不依賴任何外部庫。 -
動態鏈接:
gcc main.c -o program -L/path/to/libs -ldynamiclib
默認鏈接動態庫(優先查找.so),運行時需確保動態庫在系統路徑或通過LD_LIBRARY_PATH指定。
-
混合鏈接:
gcc main.c -o program -Wl,-Bstatic -lstaticlib -Wl,-Bdynamic -ldynamiclib
-Wl,-Bstatic
:指定后續庫靜態鏈接。-Wl,-Bdynamic
:恢復為動態鏈接
2.5 不要混淆.o文件和.so文件
.o文件是目標文件,通常是編譯單個源文件后的輸出,包含機器碼和符號表,但還未經過鏈接,所以可能有未解析的符號。
.so文件是動態鏈接庫,是多個目標文件經過鏈接后生成的共享庫,可以在運行時被多個程序共享。
.o文件是編譯階段的產物,.so文件是鏈接階段的產物;.so文件在程序運行時加載,.o文件在鏈接時被合并到可執行文件或靜態庫中。
二者主要區別:
特性 | 目標文件(.o) | 動態鏈接庫(.so) |
---|---|---|
生成階段 | 編譯階段的產物(單個源文件編譯后生成) | 鏈接階段的產物(多個目標文件或源碼鏈接生成) |
內容 | 包含單個源文件編譯后的機器碼、符號表、重定位信息 | 包含多個目標文件或源碼的已鏈接代碼,具有完整的符號解析和地址分配 |
用途 | 作為中間文件,供后續鏈接生成可執行文件或庫 | 作為共享庫,供程序在運行時動態加載 |
依賴關系 | 未解析的符號需在鏈接階段解決 | 符號已完全解析,但需在運行時與主程序或其他庫動態綁定 |
文件獨立性 | 無法單獨運行,需鏈接后使用 | 可獨立存在,但需主程序調用或動態加載 |
內存共享 | 不共享,每個進程獨立加載 | 多個進程可共享同一份.so 的代碼段,節省內存 |
更新維護 | 修改后需重新編譯和鏈接 | 更新.so 后,主程序無需重新編譯(需接口兼容) |
.o文件,通過編譯單個源文件生成,包含機器碼、符號表和重定位信息。.o文件作為中間文件,以供后續鏈接器將所有.o文件合并為可執行文件或庫(可被歸檔為靜態庫.a文件,本質上是多個.o的集合)
.so文件,通過鏈接多個目標文件或源碼生成,包含完全鏈接的代碼(所有的符號已解析,除非依賴其他動態庫)、位置無關代碼(代碼可加載到任意內存地址運行)、導出符號表(聲明庫中可供外部調用的函數或變量)。.so文件是在程序運行時由動態鏈接器加載到內存的,多個程序可共享一份.so代碼,減少內存占用,同時支持庫的熱更新(即替換.so文件后重啟程序生效)
總結:
.o
文件是編譯階段的中間產物,用于后續鏈接;.so
文件是鏈接后的動態庫,用于運行時共享。.o
文件聚焦單個模塊的編譯結果,.so
文件聚焦多模塊的協作與動態加載。
3. 知識補充
【GCC】gcc編譯學習
【GDB】gdb使用