在定義全局變量和函數是,若是咱們使用 static 關鍵字修飾他們,就只可以在同一個文件內引用他們;若是咱們不使用 static 關鍵字,就能夠在其餘文件中引用他們。html
然而,當實現動態庫時,問題就變得有些複雜。api
動態庫的接口函數能夠被動態庫內的其餘文件引用,也能夠被其餘動態庫引用。而動態庫的內部函數只能被同一個動態庫內的其餘文件引用,不能被其餘動態庫引用。oracle
對於「如何讓函數能夠被動態庫內的其餘文件引用,而不能被其餘動態庫引用」的需求,static 關鍵字是無能爲力的。函數
這時,咱們就須要修改符號的可見性(visibility)。this
對於 ELF 文件來講,程序中出現的全部變量和函數都是符號(symbol)。spa
變量所在的內存單元和函數的函數體被稱做符號的定義(definition)。命令行
當咱們使用 static 關鍵字修飾變量或者函數時,咱們是在修改符號的 binding(綁定關係)。在 C 語言中,咱們一般稱之爲做用域。code
符號一共有三種 binding,分別是:cdn
binding | 含義 |
---|---|
LOCAL | 本地符號,只能在文件內被引用 |
GLOBAL | 強全局符號,能夠被其餘文件引用,並且只能在一個文件中被定義 |
WEAK | 弱全局符號,能夠被其餘文件引用,可是能夠在多個文件中被定義 |
使用 static 關鍵字修飾的全局變量和函數是 local symbol。htm
這類符號只能在同一個文件中被引用,而不能被其餘文件引用。多個文件能夠定義同名的 local 符號,可是這些符號不會互相影響。
一個動態庫中的 local symbol 和另外一個動態庫的同名 local symbol 之間不會互相影響。
不使用 static 關鍵字修飾的全局變量和函數是 global symbol 。
這類符號能在其餘文件中被引用,也能夠其餘動態庫引用。也就是說,這樣的符號在整個進程空間內有惟一的定義。
在連接時,若是多個文件中定義了重名的 global 符號,就會引起連接錯誤。
在動態加載時,若是多個動態庫定義了重名的 global 符號,那麼就只會保留其中的一個定義。這就意味着,在訪問同一個動態庫內定義的 global 符號時,有可能訪問到的是其餘動態庫中的定義。
在 ELF 文件層面,在動態庫中訪問 global symbol 都須要藉助 PLT 和 GOT,而不能直接訪問,所以速度也比訪問 local symbol 慢。
在 C 和 C++ 程序中,有如下方法能夠定義 weak symbol:
__attribute__((weak))
修飾的全局變量和函數是 weak symbol;operator new
和 operator delete
是 weak symbol; 3.若是定義了內聯函數,可是該內聯函數生成了一個獨立的函數體,那麼該符號爲 weak symbol;Weak symbol 能夠在多個文件中被定義,可是連接時只有一個定義會被保留。保留的規則是:
所以,若是用戶定義了 operator new
函數,那麼連接器就會使用用戶定義的實現,而不是標準庫中的實現。
爲了解決全局符號可能在動態庫之間互相干擾的問題,ELF 引入了符號的可見性(visibility)。
在連接成動態庫或者可執行文件時,連接器根據符號的 visibility 修改它的 binding。
Visibility 一共有 7 種,可是經常使用的只有 default 和 hidden 兩種。它們的修飾符分別是:
__attribute__((visibility ("default")))
__attribute__((visibility ("hidden")))
默認的 visibility 是 default,可是能夠在編譯時傳入命令行參數 -fvisibility=hidden
將默認 visibility 設置爲 hidden。
在連接時,符號的 binding 保持不變。
Visibility 爲 default 的 global 符號可能被其餘動態庫的同名符號覆蓋,致使在運行時訪問的是其餘動態庫中的定義,而非該動態庫內的定義。
一般,須要導出的符號的 visibility 爲 default。
這類符號在連接成動態庫或者可執行文件後,binding 會從 global 變成 local,同時 visibility 變成 default。
所以,這類符號只能在動態庫內部被訪問,而不能被其餘動態庫訪問。
對於動態庫或者可執行程序來講,全部不須要導出的符號的 visibility 都應該是 hidden。
在實現 C 和 C++ 的動態庫時,使用 -fvisibility=hidden
來編譯動態庫。
在定義 API 時,建議使用 DLL_PUBLIC
和 DLL_LOCAL
宏來控制符號的可見性,它在 Windows、Cygwin、Linux 和 macOS 上均可以正常工做:
#if defined _WIN32 || defined __CYGWIN__
#ifdef BUILDING_DLL
#ifdef __GNUC__
#define DLL_PUBLIC __attribute__ ((dllexport))
#else
// Note: actually gcc seems to also supports this syntax.
#define DLL_PUBLIC __declspec(dllexport)
#endif
#else
#ifdef __GNUC__
#define DLL_PUBLIC __attribute__ ((dllimport))
#else
// Note: actually gcc seems to also supports this syntax.
#define DLL_PUBLIC __declspec(dllimport)
#endif
#define DLL_LOCAL
#endif
#else
#if __GNUC__ >= 4
#define DLL_PUBLIC __attribute__ ((visibility ("default")))
#define DLL_LOCAL __attribute__ ((visibility ("hidden")))
#else
#define DLL_PUBLIC
#define DLL_LOCAL
#endif
#endif
複製代碼
在 C 中,可使用這個宏導出函數和變量:
// 使用 DLL_PUBLIC 修飾須要導出的符號
DLL_PUBLIC int my_exported_api_func();
DLL_PUBLIC int my_exported_api_val;
// 不使用 DLL_PUBLIC 修飾動態庫內部的符號,
// 由於默承認見性被修改成 hidden
int my_internal_global_func();
複製代碼
在 C++ 中,可使用這個宏來導出一個類:
// 使用 DLL_PUBLIC 修飾須要導出的類
class DLL_PUBLIC MyExportedClass {
public:
// 類裏面的全部方法默認都是 DLL_PUBLIC 的
MyExportedClass();
~MyExportedClass();
int my_exported_method();
private:
int c;
// 使用 DLL_LOCAL 修飾動態庫的內部符號
DLL_LOCAL int my_internal_method();
};
複製代碼
Symbol Table Section ELF 文件中符號表的定義,詳細描述了 binding 與 visibility。
Visibility GCC wiki 中關於 visibility 的最佳實踐。