理解原始碼如何變成可執行檔,是除錯連結錯誤、優化建置速度、處理跨模組符號衝突的基礎。本文以 VC++ 專案 EX10 (含 EX10.cpp, fish.cpp, animal.cpp) 為例,詳細說明**預處理 → 編譯 → 目標檔 → 鏈接 → EXE** 完整流程與中間產物。
Compiling...
EX10.cpp
fish.cpp
animal.cpp
Linking...
EX10.exe
#include, #define, #if 等指令,展開巨集、插入標頭檔內容、移除註解,輸出**翻譯單元** (邏輯上單一巨大原始檔)。.obj)。
.cpp 獨立編譯 → 對應一個 .obj。#include 被插入翻譯單元。.obj、指定的 .lib (靜態庫/導入庫)、系統庫。.obj 中的外部參考 (如 animal::eat()) 解析到定義處。
+----------+ +------------+ +--------+ +----------+ +-----+
| EX10.cpp | --> | 預處理器 | --> | 編譯器 | --> | EX10.obj | |
+----------+ +------------+ +--------+ +----------+ |
|
+----------+ +------------+ +--------+ +----------+ | +-----------------+
| fish.cpp | --> | 預處理器 | --> | 編譯器 | --> | fish.obj | --+->| 鏈接器 |
+----------+ +------------+ +--------+ +----------+ | | - 符號解析 |
| | - 地址指派 |
+----------+ +------------+ +--------+ +----------+ | | - 產生 EXE |
|animal.cpp| --> | 預處理器 | --> | 編譯器 | --> |animal.obj| --+->| - 輸出 EX10.exe |
+----------+ +------------+ +--------+ +----------+ +-----------------+
^ |
| 靜態庫 (.lib) / 系統庫 |
+--------------------------------------------------------------+
| 觀念 | 說明 |
|---|---|
| 編譯單位 = .cpp + 所含 #include | 每個 .cpp 獨立編譯,互不干擾。標頭檔重複包含在多個 .cpp 會導致程式碼膨脹 (模板/inline 函數)。 |
| .obj 不包含最終位址 | 目標檔使用相對位移,最終虛擬位址由鏈接器指派 (ASLR 可能再隨機化)。 |
| 外部符號 = 宣告無定義 | extern int g; 在 .obj 中標記為外部符號,鏈接時必須在其他 .obj/.lib 找到定義 int g = 0;。 |
| 內聯函數可能不產生符號 | inline 函數若被內展,目標檔不含其符號;若取址或無法內展則會產生弱符號 (COMDAT)。 |
| 鏈接順序影響靜態庫解析 | 傳統鏈接器單向掃描 .lib,若 A.lib 依賴 B.lib,命令列須 A.lib B.lib 或重複列出。 |
專案 Debug/ 目錄下可見:
EX10.obj, fish.obj, animal.obj — 目標檔EX10.pdb — 程式資料庫 (除錯符號)EX10.ilk — 增量鏈接狀態EX10.exe — 最終可執行檔vc60.pdb / vc70.pdb — 編譯器產生的型別資訊 (舊版)| 錯誤類型 | 階段 | 典型原因 |
|---|---|---|
| 語法錯誤 / 型別不符 | 編譯 | 原始碼錯誤、標頭檔不一致 |
| 未定義識別符 | 編譯 | 缺少 #include、拼寫錯誤 |
| LNK2001 未解析的外部符號 | 鏈接 | 缺少 .lib、函數僅宣告未定義、專案未加入 .cpp |
| LNK2005 符號重複定義 | 鏈接 | 多個 .cpp 定義同一全域函數/變數、標頭檔定義非 inline 變數 |
| LNK1104 無法開啟檔案 | 鏈接 | .lib 路徑錯誤、檔案被佔用、磁碟空間不足 |
編譯將**翻譯單元**轉為**目標檔**,鏈接將**目標檔 + 庫**合併為 **PE EXE**。掌握此流程,可精準定位建置錯誤、理解符號可見性、優化建置依賴關係 (如前置宣告減少 #include、統一 PCH)、正確管理靜態/動態庫鏈接順序。