理解編譯鏈接過程是解決「莫名其妙的鏈接錯誤」與「運行時 coredump」的關鍵。本文從編譯單元、目標文件結構、符號表機制、鏈接器工作流程等維度,系統梳理 C++ 編譯鏈接核心原理。
.cpp 文件及其包含的頭文件構成一個獨立編譯單元。單元間相互隔離、互不可知。.obj/.o),包含:機器碼、數據、未解決符號表、導出符號表、地址重定向表。記錄本單元引用但未定義的符號及其在目標文件中的位置(偏移量)。例如 extern int n; 在 B.cpp 中會產生未解決符號 n。
記錄本單元定義且對外可見的符號及其地址。例如 A.cpp 定義 int n = 1; 會導出符號 n。
記錄目標文件內部所有對自身地址的引用。鏈接器據此將相對偏移轉換為最終虛擬地址,避免多目標文件地址衝突(如皆從 0x0000 開始)。
// A.cpp
int n = 1;
void FunA() { ++n; }
// B.cpp
extern int n;
void FunB() { ++n; }
編譯階段:
n@0x0000, FunA@0x0004;無未解決符號。FunB@0x0000;未解決符號 n@0x0001(FunB 內引用 n 的位置)。鏈接階段:
n。n,在 A.obj 找到地址 0x0000。FunB 中對 n 的引用)。| 錯誤 | 原因 | 解決 |
|---|---|---|
| unresolved external symbol | 聲明了符號但無定義,或缺少庫文件 | 提供定義、添加庫依賴、檢查拼寫 |
| duplicate symbol | 多處定義同一外部符號(如頭文件定義變量/非 inline 函數) | 頭文件僅聲明、定義放入 cpp、用 inline/static/匿名命名空間 |
externstatic (全局/命名空間作用域)inline (C++17 起)頭文件被多個 cpp 包含 → 多個編譯單元重複定義同一外部符號 → duplicate symbol。若必須在頭文件定義,需加 static/inline/const 使其內部鏈接或允許重複定義。
類定義通常在頭文件,就地初始化等同於頭文件定義非 const 靜態變量 → 多單元重複定義。C++11 允許 static constexpr 就地初始化(隱式 inline)。
編譯單元隔離,編譯器需看到函數體才能內聯展開。若定義在 cpp,其他單元無法展開,只能產生普通函數調用。
編譯器會在每個包含該頭文件的單元生成非導出的函數實體(類似 static),各自調用自己的副本,不產生鏈接衝突。