🎯 什麼情境該想到我

當你遇到「編譯過但連結失敗、undefined symbol、找不到入口」這類錯誤,或想搞懂「原始碼怎麼變成能跑的程式」時。

⚙️ 怎麼用(理解四階段,對症下藥)

gcc hello.c 這一句其實是四個階段的縮寫,而且每一階段都能單獨跑出來看(PDF p63–66):

階段實際程式拆出來看這一關出錯的意思
預處理cppgcc -E hello.c -o hello.i標頭/巨集展開錯
編譯cc1(C)/ cc1plus(C++)gcc -S hello.i -o hello.s語法/型別問題
彙編asgcc -c hello.s -o hello.o組語層問題
連結ld(GCC 實際呼叫的是 collect2ld ...符號有引用、沒定義

gcc 本身只是包裝,依參數去叫上面這些後端程式。

預處理只做 6 件事(p64):展開 #define、處理 #if/#ifdef/#elif/#else/#endif、遞迴插入 #include、刪註解、加行號與檔名標記、保留 #pragma

書中直接點出可執行動作:「所以当我们无法判断宏定义是否正确或头文件包含是否正确时,可以查看预编译后的文件来确定问题」(p65)。巨集或標頭出問題時的第一動作是看 .i 檔,不是靠猜。

編譯階段內部是六步(p66–72):掃描(詞法)→ 語法分析 → 語義分析 → 源碼優化 → 代碼生成 → 目標代碼優化。中間代碼是前端/後端的分界線——前端產機器無關的中間碼,後端轉成目標機器碼,所以跨平台編譯器能一個前端配 N 個後端(p72)。這也解釋了錯誤的分層:括號不匹配是語法錯(p68)、型別不匹配是語義錯(p70)、除以 0 是執行期錯。

連結不是一步,是三個子步驟(p76):

  1. 位址與空間分配(Address and Storage Allocation)
  2. 符號決議(Symbol Resolution)
  3. 重定位(Relocation)

「重定位所做的就是给程序中每个这样的绝对地址引用的位置”打补丁”,使它们指向正确的地址。」(p78)

術語辨析:「決議 Resolution」偏靜態連結、「綁定 Binding」偏動態連結(p76)——讀 ld 與 glibc 文件時(例如 -z now 的 “bind now”)常撞到這組詞。內部機制見 工具-符號決議與重定位

裝載階段做的是建立映射,不是搬資料(p182–183):建立獨立虛擬位址空間 → 建立「虛擬空間 ↔ 可執行檔」的映射 → 把指令暫存器設成入口位址。做完這三步一個位元組都還沒進記憶體,靠 page fault 按需載入。

  • 靜態連結時入口是 ELF header 的 e_entry動態連結時入口是動態連結器(p200、p244)。
  • 而入口也不是 main——中間還隔著執行庫的初始化,見 工具-程式啟動與執行庫

🔎 undefined symbol 診斷樹

同一句錯誤訊息,有三個完全不同的發生時間點

時間點長什麼樣常見成因
連結期ld: undefined reference to 'foo'①少連某個庫 ②輸入目標檔路徑不對 ③符號的宣告與定義不一致(C↔C++ mangling、呼叫慣例不同)(p133、p323–324)
執行期・啟動時error while loading shared libraries / undefined symbol.so 找不到或版本不對(p266)
執行期・第一次呼叫時跑了一陣子才爆延遲綁定——用 LD_BIND_NOW / RTLD_NOW 可以把它提前(p225–227、p248)

先分清楚是哪一關,再往下查。

🧰 工具鏈速查

想做什麼LinuxWindows
看段結構readelf -S / objdump -hdumpbin /HEADERS
執行視圖的 Segmentreadelf -l
看符號nm / readelf -s / objdump -tdumpbin /SYMBOLS
看重定位項objdump -r
反解 C++ 修飾名c++filtUnDecorateSymbolName()
看動態依賴ldd / readelf -ddumpbin /IMPORTS
追裝載與綁定LD_DEBUG=libs|bindings|statistics|help
看靜態庫內容ar -t / ar -xlib /LIST
瘦身strip(或 -Wl,-s 全部/-Wl,-S 只去除錯符號)

🧪 我實際套用的紀錄

  • 2026-07-15:(待填)

⚠️ 注意

  • 「編譯錯」與「連結錯」是不同階段的問題,訊息也不同——先分清楚是哪一關。
  • -g 產生的 .debug_* 段往往比程式碼本身大好幾倍,發布前記得 strip(p119–120)。但別對要除錯的產物做——書中也警告不要為了瘦身而 strip 掉共享庫的除錯資訊或用 -fomit-frame-pointer,「没有帧指针之后,无法准确定位函数的调用轨迹(Stack Trace)」(p314)。
  • -nostartfiles / -nostdlib 會讓全域物件的建構與解構不執行(p369、p388)。裸機/韌體情境要自己補。
  • 這四階段是心智模型,不是實作規格。連結階段內部的兩趟掃描、裝載階段的按需分頁,各自都是獨立的一整套機制。

🔗 相關工具