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

符號可見性(symbol visibility)

當你建構共享函式庫時,裡面的名字有兩種受眾。有些函式是公開介面——你希望其他程式呼叫的那些。許多其他則是只在函式庫內部才有意義、絕不該被暴露的私有輔助函式。符號繫結(local/global/weak)管的是單一程式的連結。符號可見性是另一層更精細的控制,管的是共享物件的哪些全域符號在載入時被匯出給外界。

ELF 為每個符號定義了一個可見性屬性,有幾個值。DEFAULT 表示符號照常被匯出,能被看見,而且重要的是能被其他模組插入取代(interpose)——這是常見的公開行為。HIDDEN 表示符號根本不放進動態符號表,所以它在自己的共享物件之外不可見——非常適合私有輔助函式。PROTECTED 表示符號「會」被匯出、對外可見,但同一共享物件「內部」對它的參照總是繫結到這個定義、永遠不會被別的模組插入取代,這讓編譯器能產生較快的呼叫。在原始碼裡你用編譯器屬性設定這些,例如 __attribute__((visibility("hidden"))),也可以用旗標 -fvisibility=hidden 翻轉整個專案的預設,再把公開 API 標為 default。

它基於三個實際理由而重要:較小的動態符號表使函式庫載入更快、隱藏內部可防止兩個碰巧用了同名輔助函式的函式庫意外衝突,而且它讓編譯器能最佳化那些不再能被插入取代的呼叫。一個常見混淆是把繫結與可見性搞混:一個符號可以是 GLOBAL(所以在靜態連結中會跨檔案解析)卻是 HIDDEN(所以不從 .so 匯出)。把你的內部 API 標為 hidden,是你在認真建構共享函式庫時能做的最便宜、槓桿最高的事情之一。

// 以 -fvisibility=hidden 建構,讓內部保持私有, // 然後只匯出公開進入點: #define API __attribute__((visibility("default"))) API int lib_open(const char *path); // 匯出 static int helper(int n); // 反正隱藏(local)

預設隱藏,再對公開 API 加上明確的 'default' 標記,給共享函式庫一個小而乾淨的匯出表面。

可見性不等於繫結:一個符號可以是 GLOBAL 卻是 HIDDEN(在靜態連結內可解析,但絕不從 .so 匯出)。PROTECTED 會匯出符號,但禁止從同一物件內部對它做插入取代。

又稱
ELF visibilitydefault/hidden/protected預設/隱藏/受保護可見性