被包裝函式

被包裝函式

被包裝函式是指源程式的單個函式或單個數據被編譯為.obj檔案中一個單獨的節(section),這樣的被稱之為COMDATA。

基本定義

被包裝函式(packaged functions)是指源程式的單個數據或單個函式被編譯為.obj檔案中一個單獨的節(section),稱之為COMDATA,因此該函式或數據可被連結器從.obj檔案中單獨抽取出來、放入最終生成的執行檔(.exe)中。這可以避免連結器把未被引用的函式加入到執行檔中,從而減小最終生成的執行檔的尺寸;還可用於刪除從不同.obj檔案中導入的同一個函式或同一個數據的多個副本。

函式級連結

假設a.cpp中定義了兩個函式f()與g();b.cpp中定義了main()函式,調用了f()。那么在連結時,連線器把a.obj中的f()與g()都加入了執行檔(.exe)中,即使g()從未被使用。這造成了執行檔的尺寸過大與浪費。

實際上在連結時從庫檔案(.lib)中抽取代碼,也不是以庫函式為單位,而是在使用lib.exe生成庫時的.obj檔案為單位,加入到最終的執行檔中,同樣也會造成執行檔的不必要的尺寸膨脹。因此,很多庫在編程、編譯時,都是把每個主要函式作為一個單獨的編譯單元,編譯成一個單獨的.obj檔案,再匯聚生成庫檔案。

上述兩種情況都是以.obj級別的連結。實際上,在一個.obj檔案內部,多個函式都放在稱作.text的代碼段中,連結器是無法從.text段中分割開每一個函式的,只能把整個.text加入到最終的執行檔中。

為此,C++編譯器提供了機制,把程式中的每個函式都被編譯放入.obj檔案的單獨的節(section)中,這樣在連結時連結器可以單獨選擇、抽取所需的每個函式的目標碼(object code)放入最終生成的執行檔。避免了在執行檔中存在從未被調用的函式的目標碼,造成檔案尺寸膨脹。這稱之為 函式級連結(Enable Function-Level Linking) 。 這種在編譯連結時被單獨包裝的函式,就是COMDATA。

對象的摺疊

全局函式或全局對象,應該只有唯一的一份定義(definition)。但是在C++中,由於各種原因,某些全局可訪問的函式或對象,在每個編譯單元中可能都定義了一份。例如:

內聯函式(inline function)總是要被包裝為COMDATA,因為它可能被取函式地址從而被當作普通非內聯函式調用。

C++類聲明中定義的成員函式是默認內聯函式,也必然被包裝為COMDATA。

C++類的虛表(vtable),也是必須包裝為COMDATA的。因為一個全局類的虛表是全局數據對象,在最終程式中只應該存在一份。但是,每個編譯單元通過包括(include)這個類的聲明,實際上都定義了一份這個類的虛表,因此在連結器合併這些編譯單元時必須刪除重複的類的虛表,只保留唯一的一份虛表。

由模板實例化得到的函式與類,都必須包裝為COMDATA。這也是因為每個編譯單元都可能會單獨實例化同一個模板。因此在連結時必須刪除這些重複的由模板實例化得到的函式與類。

連結器在生成執行檔時,合併這些重複的COMDATA,被稱作 被包裝函式的摺疊(identical COMDAT folding),簡寫為ICF。連結選項/OPT:ICF令連結器執行此種摺疊。

其它函式需要明確告知編譯器(編譯選項/Gy)是否作為包裝的COMDATA。編譯時使用選項/Gy,則該編譯單元內所有的函式都被包裝為COMDATA。

在源程式中使用__declspec(selectany),可以把單個的函式或數據用作COMDATA。

連結選項

/OPT:REF 刪除從未被使用的函式或數據。

/OPT:NOREF 保留這些未被使用的函式或數據。

連結器默認刪除未被使用的函式或數據。使用選項/DEBUG連結時,默認是保留未被使用的函式或數據。

示例

只有具有外部可訪問性的全局數據才能用__declspec(selectany)聲明為COMDATA。例如:

//正確。 x1被初始化且具有外部可見性(externally visible )

__declspec(selectany) int x1=1;

//不正確 - const全局變數在C++中預設為static,

//因此x2是外部不可見的。

//編譯時產生錯誤C2496:'selectany' can only be applied to data items with external linkage

//但這在C語言中卻是正確的

const __declspec(selectany) int x2 =2;

//正確- x3是extern const, 因此具有外部可見性

extern const __declspec(selectany) int x3=3;

//正確 - x4 is extern const, so it is externally visible

extern const int x4;

const __declspec(selectany) int x4=4;

//不正確 - __declspec(selectany) is applied to the uninitialized declaration of x5

//可以解釋為x5是在別的編譯單元定義的,因此在本地聲明為selectany沒有必要

//但是它卻成功通過編譯!

extern __declspec(selectany) int x5;

// 正確: dynamic initialization of global object

class X {

public:

X(int i){i++;};

int i;

};

__declspec(selectany) X x(1);

相關詞條

熱門詞條

聯絡我們