控制符號的可見性

2021-05-24 14:06:40 字數 4463 閱讀 3825

在普通的c語言中,如果您希望將函式或者變數限制在當前檔案中,需要對其使用static關鍵字。然而,在乙個包含很多檔案的共享庫中,如果您希望某個符號可以被共享庫內部的幾個檔案訪問,而又不提供給外部,則對符號進行隱藏處理就會比較困難。大多數的聯結器都提供一些便利的方法來隱藏或者顯示模組中所有的符號,但如果希望更加具有選擇性,則需要更多的處理。

在mac os x v10.4之前,有兩種機制可以控制符號的可見性。

第一種技術是通過__private_extern__關鍵字,將符號宣告為共享庫私有,而又可以從當前檔案輸出。這個關鍵字可以使用到static或extern關鍵字可以使用的任何地方;

第二種技術是使用輸出列表。輸出列表是乙個檔案,含有您希望隱藏或者顯示的符號名稱。c語言的符號名稱比較容易確定(在名稱前面加乙個下劃線字元就可以),c++的符號名稱的確定則要複雜得多。由於有類和名字空間,編譯器必須包含更多的資訊才能唯一標識每乙個符號,編譯器也因此為每個符號建立乙個所謂的重整名稱(mangled name)。這種重整名稱的生成規則通常和編譯器有關,難於進行邏輯的推斷,也很難在共享庫定義的大列表中找到。

幸運的是,gcc 4.0提供了一些新的、用於改變符號可見性的方法。下面部分將對這些新方法進行描述:

一、使用gcc 4.0來識別符號號的可見性

從mac os x v10.4開始,要隱藏c++符號名稱就簡單很多了。gcc 4.0編譯器支援一些用於隱藏和顯示符號名稱的新選項,同時還支援乙個新的pragma和一些編譯器屬性,用於改變**符號的可見性。

請注意:下面的特性只在gcc 4.0和更新的版本提供。

gcc 4.0支援乙個新的選項,用於設定原始檔中符號的預設可見性。這個選項是-fvisibility=vis ,您可以用它來設定當前編譯的符號的可見性。這個選項的值可以是default(預設)或者hidden(隱藏),設定為default時,沒有顯式標識為hidden的符號都處理為可見;設定為hidden時,沒有顯式標識為可見的符號都處理為隱藏。如果您在編譯中沒有指定-fvisibility選項,編譯器會自行處理為預設的可見性。

請注意:default設定不是指編譯器預設的處理方式。和hidden設定一樣,default來自elf格式定義的可見性名稱。具有default可見性的符號和所有不使用特殊機制的符號具有相同的可見性型別&8212;也就是說,它將作為公共介面的一部分輸出。

編譯器還支援-fvisibility-inlines-hidden選項,用於強制隱藏所有的嵌入函式。當您希望對大多數專案使用預設的可見性,但又希望隱藏所有的嵌入函式時,您可能會用到這個選項。

如果用gcc 4.0編譯**,可以用可見性屬性將個別的符號標識為default或hidden:

__attribute__((visibility("default"))) void myfunction1() {}    

__attribute__((visibility("hidden"))) void myfunction2() {}

可見性屬性會覆蓋編譯時通過-fvisibility選項指定的值。因此,增加default可見性屬性會使符號在所有情況下都被輸出,反過來,增加hidden可見性屬性會隱藏相應的符號。

可見性屬性可以應用到函式,變數,模板,以及c++類。如果乙個類被標識為hidden,則該類的所有成員函式,靜態成員變數,以及編譯器生成的元資料,比如虛函式表和rtti資訊也都會被隱藏。

請注意:雖然模板宣告可以用可見性屬性來標識,但是模板例項則不能。這是個已知的限制,在gcc的未來版本中可能被修復。

為了演示這些屬性如何在編譯時進行工作,請看一下下面的宣告:

int a(int n)

__attribute__((visibility("hidden"))) int b(int n)

__attribute__((visibility("default"))) int c(int n)

class x ;

class __attribute__((visibility("hidden"))) y ;

class __attribute__((visibility("default"))) z ;

x::~x()

y::~y()

z::~z()

用-fvisibility=default選項編譯這段**會使函式a和c ,還有類x和z的符號輸出到庫的外部。而如果用-fvisibility=hidden選項進行編譯,則會輸出函式c和類z的符號。

從實踐上看,用可見性屬性識別符號號的可見或者隱藏比起用__private_extern__關鍵字隱藏個別的符號要好。__private_extern__關鍵字採用的方法是預設暴露所有的符號,然後選擇性地隱藏所有的符號。在大型的共享庫中,相反的方法通常更好一些。因此,隱藏所有的符號,然後有選擇地暴露希望客戶使用的符號通常會好一些。

為了簡化將符號標識為輸出的任務,您可能希望將default的可見性屬性集合定義為巨集,如下面的例子所示:

#define export __attribute__((visibility("default")))

// always export the following function.

export int myfunction1();

使用巨集的優點是當您的**也需要在其它平台編譯的時候,可以將巨集改為適用於其它平台的關鍵字。

將符號標識為default或者hidden的另外一種方法是使用gcc 4.0新引入的pragma指令。gcc可見性pragma的優點是可以快速地標識一整塊函式,而不需要將可見性屬性應用到每個函式中。這個pragma的用法如下:

void f()

#pragma gcc visibility push(default)

void g()

void h()

#pragma gcc visibility pop

在這個例子中,函式g和h被標識為default,因此無論-fvisibility選項如何設定,都會輸出;而函式f則遵循-fvisibility選項設定的任何值。push和pop兩個關鍵字標識這個pragma可以被巢狀。

二、限制符號可見性的原因

從動態共享庫中盡可能少地輸出符號是乙個好的實踐經驗。輸出乙個受限制的符號會提高程式的模組性,並隱藏實現的細節。在庫中減少符號的數目還可以減少庫的記憶體印跡,減少動態聯結器的工作量。動態聯結器裝載和識別的符號越少,程式啟動和執行的速度就越快。

三、嵌入函式的可見性

嵌入函式通常在呼叫現場被展開,因此在目標檔案中完全不表示為符號。然而在很多情況下,由於一些很好的理由,編譯器可能會表示出嵌入函式的函式體,並因此為其生成符號。最為常見的情況是如果所有的優化都被禁止了,編譯器可能決定不進行嵌入優化;較為少見的情況是函式太大,不能進行嵌入,或者函式的位址在其它地方被使用,因此需要有乙個符號。

雖然在c++中,您可以將可見性屬性(參見「可見性屬性」部分)應用到嵌入函式中,就如同其它符號,但是隱藏所有的嵌入函式通常要更好一些。從動態共享庫中輸出嵌入函式會帶來一些複雜的問題。因為有幾個因素跟編譯器的選擇(是將函式表示出來,還是進行嵌入處理)有關係,和共享庫的不同連編版本一起連編客戶程式的時候,可能會碰到錯誤。

c和 c++的嵌入函式語法有一些細微的區別,記住這一點也是很重要的。在c程式中,只有乙個源**檔案可以為乙個嵌入函式提供out-of-line(譯者注:與inline相對應,指不進行嵌入展開)的定義。這意味著c程式設計師對out-of-line的拷貝駐留的位置有精確的控制。因此對於基於c的動態共享庫來說,只輸出嵌入函式的乙個拷貝是可能的。對於c++,嵌入函式的定義必須包含在每個使用該函式的翻譯單元中。因此如果編譯器表示了乙個out- of-line的拷貝,則可能會有幾個拷貝駐留在不同的翻譯單元中。

最後,如果希望隱藏所有的嵌入函式(但不希望隱藏所有的其它**),可以用在**編譯的時候使用-fvisibility-inlines-hidden選項。如果您已經向編譯器傳遞了-fvisibility=hidden選項,則沒有必要使用-fvisibility-inlines-hidden選項。

四、符號可見性和objective-c

objective- c是c的限制超集, objective-c++則是c++的限制超集。這意味著這裡關於c和c++符號可見性的所有討論都同樣可以應用到objective-c和 objective-c++上。您可以用編譯器選項,可見性屬性,以及可見性pragma來隱藏objective-c**檔案中的c和c++**。然而,這些可見性控制只能應用到**中的c或者c++子集,不能應用到objective-c的類和方法上。

objective-c類和訊息名稱由objective-c執行環境來限制,而不是通過聯結器,因此可見性的說明對它們是不起作用的。我們無法在共享庫的客戶程式中隱藏共享庫定義的objective-c類。

C C 中控制動態庫的符號可見性

寫了乙個動態庫供客戶使用,此庫使用了一些第三方靜態庫,無奈客戶也使用了這些第三方庫,從而產生了符號衝突。所以需要隱藏此庫中第三方庫的匯出符號。static 關鍵字 匯出列表 指定 visibility 屬性 最終選擇方案三,即利用gnu的visibility 屬性。1 修改cmakelist.txt...

PHP 訪問控制(可見性)

define myclass class myclass obj new myclass echo obj public 這行能被正常執行 echo obj protected 這行會產生乙個致命錯誤 echo obj private 這行也會產生乙個致命錯誤 obj printhello 輸出 p...

GCC的符號可見性 解決多個庫同名符號衝突問題

問題 最近專案遇到一些問題,場景如下 主程式依賴了兩個庫liba的funca函式和libb的funcb函式。示意的 main.cpp 如下 include int funca int,int int funcb int,int int main liba示意實現 liba.cpp 如下 int su...