連結器、載入器與目的檔格式

符號解析(symbol resolution)

一個程式通常由許多目的檔與函式庫建構而成,每一個都提到名字——呼叫 foo()、讀取某個全域設定等等。符號解析是配對步驟,連結器在此把對某名字的每次使用(每個未定義參照)與輸入中某處「恰好一個」定義配成對。成功時,每次呼叫與每次變數存取都指向真實位址;失敗時,你會得到熟悉的 'undefined reference' 或 'multiple definition' 錯誤。

連結器走過它的輸入並維護一張符號表。對每個未定義的 GLOBAL 符號,它必須找到「一個且僅一個」強定義。指導原則是單一定義規則:整個連結中,每個全域名字應恰有一個強定義。弱定義使之變柔:若沒有強定義,就用弱的;若有強的,強者勝出、弱者被忽略。封存(.a)函式庫加了一道皺摺:靜態函式庫是一袋 .o 檔,連結器只拉入那些能滿足某個仍未解析符號的成員,並由左到右處理輸入,這就是為什麼命令列上的連結順序可能有影響(函式庫通常必須出現在使用它的物件之後)。動態函式庫延後其中一部分:將由共享物件提供的未定義符號會被記下,之後由動態連結器在載入時解析。

它重要,是因為解析是「分離編譯」最終被縫合成一個連貫程式的地方——也是一大部分建構失敗所在之處。一個精確的心智模型:'undefined reference to foo' 表示解析為 foo 找到零個定義;'multiple definition of foo' 表示它找到兩個或更多強定義。一個微妙的真實世界危害是 C++ 的單一定義規則被默默違反:當兩個編譯單元用不同方式定義同一個 inline 實體時,連結器可能任意挑一個,產生一個能建構卻行為錯誤的程式。

$ gcc main.o -lm # 可行:-lm 出現在 main.o 之後 $ gcc -lm main.o # 可能失敗:看到 -lm 時, # main.o 對 sqrt 的使用尚未被記下 # error: undefined reference to `sqrt'

對靜態函式庫,順序有影響:連結器只拉入能滿足它已看作未定義之符號的封存成員。

'undefined reference' 表示找到零個定義;'multiple definition' 表示找到兩個或更多強定義。弱定義不會觸發 'multiple definition'——它只是默默輸給任何強定義。

又稱
one-definition rulename resolution單一定義規則