靜態連結與動態連結
當你的程式用到某個函式庫時,「函式庫的程式碼何時併入你的程式」存在一個選擇。你可以在建置時就把它烤進去,把函式庫程式碼直接複製進你的可執行檔;或者留一個引用,等程式執行時才把函式庫找出來並接上。這兩個答案就是靜態連結(static linking)與動態連結(dynamic linking)。食譜的比喻:要嘛把你需要的每份食譜都影印進自己的活頁夾(靜態),要嘛只寫下「實際下廚時去架上拿哪本共用食譜」(動態)。
靜態連結時,連結器把需要的函式庫常式複製進最終的可執行檔。結果是一個自給自足的單一檔案,帶著它需要的一切;它執行時沒有外部相依,函式庫版本在建置時就被凍結。動態連結時,可執行檔改為記下它所需共享函式庫的名稱;執行時動態載入器找到那些函式庫(在 Linux 上是 .so、在 Windows 上是 .dll 這類檔案),把它們映射進記憶體、再修補引用。如此一來,許多執行中的程式可以共用記憶體裡的同一份函式庫,而更新那個共享函式庫(比如一個資安修補)就能升級每一支用到它的程式,無需重新建置它們。
每個選擇都是誠實的取捨,沒有絕對的贏家。靜態連結帶來較大的可執行檔、各程式間重複的程式碼,但換來最大的可攜性,以及對「到底跑哪個函式庫版本」毫無意外。動態連結帶來較小的可執行檔、可共享且可更新的函式庫,但引入了執行期相依(那令人頭痛的「找不到共享函式庫」錯誤),以及更新後的函式庫可能微妙地改變行為的風險。沒有哪一個放諸四海皆準;選擇取決於你重視的是自給自足與可預測性,還是較小的體積與便利的共享更新。
同一支程式用到數學函式庫的兩種建置: 靜態:數學常式被複製進可執行檔內。一個大檔案, 沒有外部需求,函式庫版本被凍結。 動態:可執行檔只說『我需要 libm』。啟動時動態載入器 把 libm 映射進記憶體並連結它。檔案較小、在記憶體中共享, 但若 libm 不見了就無法啟動。
靜態在建置時把函式庫烤進去;動態在執行時才找到並連結它。各自在體積與相依之間做取捨。
兩者沒有誰絕對更好。動態連結省空間、又讓一個資安修補就能修好每支程式,但增加了執行期相依;靜態連結自給自足,卻凍結了函式庫版本、並讓每個執行檔變肥。