在 Windows 上使用 MSVC 創(chuàng)建和使用動態(tài)庫時需要導(dǎo)出導(dǎo)入符號(參見Windows上動態(tài)庫符號的導(dǎo)出和導(dǎo)入),但在 Linux 上使用 GCC 時,一般好像不需要導(dǎo)入導(dǎo)出符號。其實不然,GCC 編譯時并不是不需要導(dǎo)出符號,而是默認導(dǎo)出了所有的符號。
GCC 中也存在一個符號可見性的概念,稱之為 Visibility,一般指的就是動態(tài)庫中符號的可見性。默認情況下,動態(tài)庫中的所有符號對于外部都是可見的,因此使用者可以直接使用動態(tài)庫提供的函數(shù)。但 GCC 提供了修改可見性的方式,編譯時可以通過-fvisibility參數(shù)修改默認所有符號的可見性,也可以在代碼中使用__attribute__來修改某個符號的可見性。
那既然 GCC 默認已經(jīng)讓所有符號都可見,為什么還要提供選項來隱藏符號呢?符號全部可見豈不是更方便,不用像在 Windows 上那樣麻煩的導(dǎo)出導(dǎo)入。其實不是,GCC 推薦隱藏動態(tài)庫內(nèi)部使用的符號,只把需要提供給外部使用的符號設(shè)置為可見。GCC 的解釋為:通過隱藏那些不需要外部使用的符號,可以減少動態(tài)庫的加載時間,減小動態(tài)庫文件的大小,能夠讓編譯器生成更優(yōu)的代碼,同時也能避免不同庫之間的符號沖突。所以我們在設(shè)計動態(tài)庫時,可以先通過-fvisibility參數(shù)使所有符號默認不可見,再通過__attribute__使那些需要提供給外部的符號變?yōu)榭梢姟?/p>-fvisibility
-fvisibility參數(shù)的完整形式如下:
-fvisibility=[default|internal|hidden|protected]
visibility 有四個可以設(shè)置的值,其中 internal 和 protected 很少使用到,大部分情況下只需要使用 default 和 hidden 兩個值。default 就是 GCC 的默認情況,表示所有符號都是可見的,hidden 表示設(shè)置所有符號都是不可見的。
編譯動態(tài)庫時,添加-fvisibility=hidden參數(shù),使所有符號都默認不可見,例如,
g++ -fvisibility=hidden -c foo.cpp -o foo.o__attribute__((visibility(“default”)))
__attribute__中的 visibility 屬性用于單獨修改某個符號的可見性,會覆蓋-fvisibility參數(shù)的設(shè)置。我們在頭文件中聲明時,將需要提供給外部使用的符號設(shè)為可見,例如,
__attribute__((visibility("default"))) void foo(); class __attribute__((visibility("default"))) Foo { ... };
對于庫的提供者,編譯時需要使用__attribute__((visibility("default")))來將符號設(shè)為可見,對于庫的使用者,這個關(guān)鍵字是可選的,不必像 Windows 上那樣一個導(dǎo)出一個導(dǎo)入,但我們可以使用宏來替換這個比較長的關(guān)鍵字,并且兼容 Windows 上的機制。
#ifndef FOO_H #define FOO_H #ifdef _WIN32 #define FOO_EXPORT __declspec(dllexport) #define FOO_IMPORT __declspec(dllimport) #else #define FOO_EXPORT __attribute__((visibility("default"))) #define FOO_IMPORT #endif #ifdef FOO_DLL #define FOO_API FOO_EXPORT #else #define FOO_API FOO_IMPORT #endif FOO_API void func(); class FOO_API Data { ... }; #endif
參考:
https://gcc.gnu.org/wiki/Visibility
https://gcc.gnu.org/onlinedocs/gcc/Code-Gen-Options.html