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