Walter Bright

許多程式設計師眼中的建置流程#

許多程式設計師對從原始碼到靜態連結可執行檔的過程,理解是這樣的:

  1. 編輯原始碼
  2. 將原始碼編譯成目的檔(object files)
  3. 某種神奇的事情發生了
  4. 執行可執行檔

步驟 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 列出可執行檔中所有符號及其位址,告訴你哪些模組從程式庫中被連結進來、每個模組的大小。這樣你就能看出膨脹的來源。經常會有你不知道為什麼被連結進來的程式庫模組——你可以暫時從程式庫中移除可疑模組再重新連結,產生的未定義符號錯誤會指出是誰在參考那個模組。

結語#

雖然某個特定的連結器訊息可能不總是一目了然,但連結器的機制沒有任何神奇之處。它是直截了當的——你需要弄清楚的只是每個案例中的細節。