前言
從 `.c` 源文件到可執行檔,經歷四大階段:預處理 → 編譯/優化 → 匯編 → 鏈接。理解每階段輸入產出、關鍵選項與常見問題,能大幅提升除錯與效能調優能力。
一、預處理
命令:gcc -E test.c -o test.i(或 `cpp test.c > test.i`)
核心任務
- 宏展開:`#define` 替換,`#undef` 取消
- 條件編譯:`#if` `#ifdef` `#ifndef` `#else` `#elif` `#endif` 過濾代碼
- 頭文件包含:`#include "..."` / `#include <...>` 將內容原位展開
- 特殊符號替換:`__LINE__`、`__FILE__`、`__DATE__`、`__TIME__`、`__func__` (C99)
- 刪除註解:單行/多行註解移除
產出:無宏、無條件編譯、無頭文件引用、無註解的純 C 源碼(`.i`)。
二、編譯與優化
命令:gcc -S test.i -o test.s
子階段
- 詞法/語法分析:生成抽象語法樹(AST)
- 中間代碼生成:GCC 內部 GIMPLE/RTL
- 機器無關優化(`-O1`/`-O2`/`-Os`/`-O3`):
- 公共子表達式消除 (CSE)
- 循環優化:代碼外提、強度削弱、循環展開、縱合
- 常數傳播、死碼消除 (DCE)、SSA 形式優化
- 機器相關優化:寄存器分配、指令排序、流水線/超純量調度、向量化
產出:匯編語言源文件(`.s`)。
常用優化選項
| 選項 | 效果 |
| -O0 | 無優化,預設,適合除錯 |
| -O1 | 基礎優化,平衡編譯時間與效能 |
| -O2 | 推薦發布優化,開啟幾乎所有安全優化 |
| -O3 | 激進優化(向量化、內聯閾值提高),可能增大體積 |
| -Os | 優化體積(嵌入式常用) |
| -Ofast | -O3 + 違反 IEEE 浮點標準(如 `-ffast-math`) |
三、匯編
命令:gcc -c test.s -o test.o(或 `as test.s -o test.o`)
目標文件結構 (ELF)
- 代碼段 (.text):指令,唯讀可執行
- 數據段 (.data/.bss/.rodata):全域/靜態變數,讀寫或唯讀
- 符號表 (.symtab/.dynsym):函數/變數名稱與地址映射
- 重定位表 (.rela.text 等):待解析的外部符號引用
三類目標文件
- 可重定位文件(`.o`):供鏈接器進一步處理
- 共享目標文件(`.so`):動態鏈接庫
- 可執行文件:可直接被載入器執行
四、鏈接
命令:gcc test.o -o test(內部調用 `ld`)
核心任務
- 符號解析:將每個引用綁定到唯一定義
- 重定位:修正地址引用,生成最終虛擬地址
- 段合併:同類段合併,建立段表
靜態 vs 動態鏈接
| 維度 | 靜態鏈接 (-static) | 動態鏈接 (預設) |
| 庫代碼去向 | 複製進可執行檔 | 僅記錄庫名與少量元數據 |
| 檔案體積 | 大 | 小 |
| 記憶體佔用 | 每進程獨佔 | 多進程共享單份代碼 |
| 啟動速度 | 快(無動態鏈接開銷) | 稍慢(需載入器解析) |
| 部署依賴 | 無 | 需目標機器有對應 .so |
常見鏈接問題
- 循環依賴:`libA.a` 需 `libB.a`,`libB.a` 又需 `libA.a` → 重複列出庫或用 `-( -lA -lB -)`
- 符號衝突:多個庫定義同名符號 → 使用版本腳本、隱藏符號 (`-fvisibility=hidden`)
- 缺少運行期庫:見上文運行期搜尋路徑
五、一鍵查看完整流程
gcc -v -save-temps test.c -o test
# 產出:test.i (預處理) test.s (匯編) test.o (目標) test (可執行)
六、調試資訊嵌入
-g:生成 DWARF 調試資訊(行號、變數型別、作用域)
-pg:插桩生成 `gmon.out`,配合 `gprof` 分析熱點
- 上一篇文章: GCC編譯器頭文件與庫文件搜尋路徑完整解析
- 下一篇文章: Windows x86 Shellcode 編寫實戰:C 語言實現免注入彈窗/下載/執行
Copyright ©
程式碼之家 All Rights Reserved