Walter Bright
許多程式設計師眼中的建置流程#
許多程式設計師對從原始碼到靜態連結可執行檔的過程,理解是這樣的:
- 編輯原始碼
- 將原始碼編譯成目的檔(object files)
- 某種神奇的事情發生了
- 執行可執行檔
步驟 3 當然就是連結(linking)。作者做了數十年技術支援,不斷遇到以下困擾:
- 連結器說
def被定義了多次 - 連結器說
abc是一個未解析的符號(unresolved symbol) - 為什麼我的可執行檔這麼大?
這些問題後面通常伴隨著「似乎」和「不知怎地」等措辭,暗示人們把連結過程視為只有巫師才能理解的魔法程序。
連結器其實很簡單#
連結器是一個愚蠢、平凡、直截了當的程式。它所做的就是:將目的檔的程式碼段和資料段串接在一起、將符號參考連接到它們的定義、從程式庫中拉出未解析的符號,然後寫出可執行檔。就這樣,沒有咒語,沒有魔法!
常見的連結器錯誤#
符號被定義多次#
在 C、C++、D 等語言中,有**宣告(declaration)和定義(definition)**的區分。宣告通常放在標頭檔中:
extern int iii; // 宣告:產生外部參考
定義出現在實作檔中:
int iii = 3; // 定義:實際配置儲存空間
每個符號只能有一個定義(如同電影《乃乃者》——只能有一個)。如果 iii 的定義出現在多個實作檔中,連結器就會抱怨多重定義(multiply defined)。
未解析的符號#
不僅只能有一個定義,而且必須要有一個。如果 iii 只有宣告而沒有定義,連結器會抱怨未解析的符號。
可執行檔太大#
要判斷為什麼可執行檔這麼大,可以查看連結器可選擇性產生的 map file。Map file 列出可執行檔中所有符號及其位址,告訴你哪些模組從程式庫中被連結進來、每個模組的大小。這樣你就能看出膨脹的來源。經常會有你不知道為什麼被連結進來的程式庫模組——你可以暫時從程式庫中移除可疑模組再重新連結,產生的未定義符號錯誤會指出是誰在參考那個模組。
結語#
雖然某個特定的連結器訊息可能不總是一目了然,但連結器的機制沒有任何神奇之處。它是直截了當的——你需要弄清楚的只是每個案例中的細節。