在 Windows 上使用 MSVC 創(chuàng)建和使用動(dòng)態(tài)庫(kù)時(shí)需要導(dǎo)出導(dǎo)入符號(hào)(參見(jiàn)Windows上動(dòng)態(tài)庫(kù)符號(hào)的導(dǎo)出和導(dǎo)入),但在 Linux 上使用 GCC 時(shí),一般好像不需要導(dǎo)入導(dǎo)出符號(hào)。其實(shí)不然,GCC 編譯時(shí)并不是不需要導(dǎo)出符號(hào),而是默認(rèn)導(dǎo)出了所有的符號(hào)。

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

那既然 GCC 默認(rèn)已經(jīng)讓所有符號(hào)都可見(jiàn),為什么還要提供選項(xiàng)來(lái)隱藏符號(hào)呢?符號(hào)全部可見(jiàn)豈不是更方便,不用像在 Windows 上那樣麻煩的導(dǎo)出導(dǎo)入。其實(shí)不是,GCC 推薦隱藏動(dòng)態(tài)庫(kù)內(nèi)部使用的符號(hào),只把需要提供給外部使用的符號(hào)設(shè)置為可見(jiàn)。GCC 的解釋為:通過(guò)隱藏那些不需要外部使用的符號(hào),可以減少動(dòng)態(tài)庫(kù)的加載時(shí)間,減小動(dòng)態(tài)庫(kù)文件的大小,能夠讓編譯器生成更優(yōu)的代碼,同時(shí)也能避免不同庫(kù)之間的符號(hào)沖突。所以我們?cè)谠O(shè)計(jì)動(dòng)態(tài)庫(kù)時(shí),可以先通過(guò)-fvisibility參數(shù)使所有符號(hào)默認(rèn)不可見(jiàn),再通過(guò)__attribute__使那些需要提供給外部的符號(hào)變?yōu)榭梢?jiàn)。

-fvisibility

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

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

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

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

g++ -fvisibility=hidden -c foo.cpp -o foo.o
__attribute__((visibility(“default”)))

__attribute__中的 visibility 屬性用于單獨(dú)修改某個(gè)符號(hào)的可見(jiàn)性,會(huì)覆蓋-fvisibility參數(shù)的設(shè)置。我們?cè)陬^文件中聲明時(shí),將需要提供給外部使用的符號(hào)設(shè)為可見(jiàn),例如,

__attribute__((visibility("default"))) void foo();

class __attribute__((visibility("default"))) Foo
{
    ...
};

對(duì)于庫(kù)的提供者,編譯時(shí)需要使用__attribute__((visibility("default")))來(lái)將符號(hào)設(shè)為可見(jiàn),對(duì)于庫(kù)的使用者,這個(gè)關(guān)鍵字是可選的,不必像 Windows 上那樣一個(gè)導(dǎo)出一個(gè)導(dǎo)入,但我們可以使用宏來(lái)替換這個(gè)比較長(zhǎng)的關(guān)鍵字,并且兼容 Windows 上的機(jī)制。

#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