在 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)出符號,而是默認(rèn)導(dǎo)出了所有的符號。

GCC 中也存在一個符號可見性的概念,稱之為 Visibility,一般指的就是動態(tài)庫中符號的可見性。默認(rèn)情況下,動態(tài)庫中的所有符號對于外部都是可見的,因此使用者可以直接使用動態(tài)庫提供的函數(shù)。但 GCC 提供了修改可見性的方式,編譯時可以通過-fvisibility參數(shù)修改默認(rèn)所有符號的可見性,也可以在代碼中使用__attribute__來修改某個符號的可見性。

那既然 GCC 默認(rèn)已經(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ù)使所有符號默認(rèn)不可見,再通過__attribute__使那些需要提供給外部的符號變?yōu)榭梢姟?/p>-fvisibility

-fvisibility參數(shù)的完整形式如下:

-fvisibility=[default|internal|hidden|protected]

visibility 有四個可以設(shè)置的值,其中 internal 和 protected 很少使用到,大部分情況下只需要使用 default 和 hidden 兩個值。default 就是 GCC 的默認(rèn)情況,表示所有符號都是可見的,hidden 表示設(shè)置所有符號都是不可見的。

編譯動態(tài)庫時,添加-fvisibility=hidden參數(shù),使所有符號都默認(rèn)不可見,例如,

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