內聯函數

1 VC++    1

1.1 inline__inline    1

1.2 啓用內聯    1

1.3 內聯和外聯    2

1.3.1 何時使用內聯    3

1.3.2 何時使用外聯    4

1.3.3 外聯單選    4

1.3.4 外聯並存    5

1.3.5 extern inline    6

1.4 動態庫    6

1.4.1 導出內聯函數    6

1.4.2 導入內聯函數    7

1.5 靜態庫    7

1.5.1 交叉調用外聯函數    7

1.5.2 缺少外聯函數    8

 

 

1 VC++

1.1 inline__inline

對於VC++而言,inline僅用於內聯C++函數,而__inline可用於內聯CC++函數。

1.2 啓用內聯

編譯VC++ Debug 版程序時,內聯功能默認是被禁用的。如果需要,可以啓用內聯。以VC++6.0爲例,其操作步驟如下:

Inline function expansionDisable*更改爲Only __inline。如下圖所示:

圖1.1

Debug infoProgram Database for Edit and Continue更改爲Program Database。如下圖所示:

圖1.2

1.3 內聯和外聯

查看如下代碼:

#include <STDIO.H>

 

inline void FuncInline()

{

printf("FuncInline\n");

}

 

void Func1()

{

FuncInline();

}

VC++編譯Release版(內聯被啓用),Func1裏的代碼FuncInline();將被展開爲printf("FuncInline\n");此即爲FuncInline的內聯版本。

VC++編譯Debug版(內聯被禁用),Func1裏的代碼FuncInline();將調用函數FuncInline,此即爲FuncInline的外聯版本。

使用UltraEdit打開Release版和Debug版生成的obj文件,就會發現:Debug版的obj文件裏能查找到字符串FuncInline,而Release版的obj文件裏就查不到字符串FuncInline。這說明:在啓用內聯功能的情況下,編譯器一般不會生成內聯函數的外聯代碼。但也有特殊情況,如下面的代碼:

#include <STDIO.H>

 

inline void FuncInline()

{

printf("FuncInline\n");

}

 

void Func1()

{

void(*pFunc)() = &FuncInline;

(*pFunc)();

}

Func1裏首先獲得函數FuncInline的指針,然後再調用此函數。此時,即使啓用了內聯功能也必須生成FuncInline的外聯代碼,且pFunc指向的是外聯代碼,(*pFunc)();執行的是FuncInline的外聯版本。

1.3.1 何時使用內聯

參考下面的代碼:

#include <STDIO.H>

 

inline void FuncInline()

{

printf("FuncInline\n");

}

 

void Func1()

{

FuncInline();

}

只有內聯功能被啓用,且FuncInline能夠被內聯的時候,Func1裏的FuncInline();纔會使用內聯代碼。

若內聯功能被禁用(如:編譯Debug版)或FuncInline太複雜而無法內聯時,Func1裏的FuncInline();將使用外聯版本,其實就是函數調用。

1.3.2 何時使用外聯

1、通過函數指針調用函數,則一定使用外聯版本。

2、只有函數聲明,沒有函數實現的內聯函數,一定使用外聯版本。如下面的代碼:

inline void FuncInline();

 

void Func()

{

FuncInline();

}

雖然FuncInline被聲明爲內聯函數,但是這個編譯單元(編譯前是ccpp文件,編譯後就是obj文件)沒有該函數的內聯代碼,因此Func裏的FuncInline();使用的是外聯版本。

1.3.3 外聯單選

假定一個VC++項目包含如下源文件:

1.cpp

#include <STDIO.H>

 

inline void FuncInline()

{

printf("inline in 1.cpp\n");

}

 

void Func1()

{

void(*pFunc)() = &FuncInline;

FuncInline();

printf("Func1 %p\n",pFunc);

(*pFunc)();

}

2.cpp

#include <STDIO.H>

 

inline void FuncInline()

{

printf("inline in 2.cpp\n");

}

 

void Func2()

{

void(*pFunc)() = &FuncInline;

FuncInline();

printf("Func2 %p\n",pFunc);

(*pFunc)();

}

VC++編譯Debug版(內聯被禁用),此時1.obj2.obj均有FuncInline的外聯版本。Func1Func2裏的代碼FuncInline();究竟會調用哪個obj文件裏的FuncInline?答案是連接時的第一個,其它的均被捨棄。此時,Func1Func2裏的pFunc是相等的。

注意:VC++只能單選inline函數,不能單選外部函數。假定有如下源代碼文件:

3.cpp

#include <STDIO.H>

 

void FuncInline()

{

printf("FuncInline in 3.cpp\n");

}

 

void Func3()

{

void(*pFunc)() = &FuncInline;

FuncInline();

printf("Func3 %p\n",pFunc);

(*pFunc)();

}

VC++編譯Debug版(內聯被禁用)時,3.obj裏將有外部函數FuncInline的二進制代碼。此時將有三個FuncInline:一個是3.obj裏的外部函數;另兩個是1.obj2.obj裏的外聯函數。外聯函數可以單選,但是外聯函數與外部函數是不能單選的。這就造成連接時編譯器無法確定Func1Func2Func3究竟要使用外聯函數還是外部函數,從而導致編譯失敗。

1.3.4 外聯並存

使用static inline可以保留一個內聯函數的所有外聯版本,其功能爲:修飾內聯函數的外聯版本爲static函數。

舉例說明:現在修改1.cpp2.cppinlinestatic inline

VC++編譯Debug版(內聯被禁用),此時1.obj2.obj均有FuncInline的外聯版本,但是它們都是static函數。因此,相同的代碼FuncInline();,在Func1裏會調用1.obj裏的FuncInline,在Func2裏會調用2.obj裏的FuncInline。同樣的,Func1Func2裏的pFunc也分別指向1.obj2.obj裏的FuncInline

顯然:外聯單選時,不會單選static inline函數的外聯版本。

1.3.5 extern inline

根據static inline可以理解extern inline的功能爲:修飾內聯函數的外聯版本爲extern函數(即外部函數)。事實上,不使用extern關鍵字,外聯函數就是外部函數。因此,VC++extern inlineinline是沒有任何分別的。

1.4 動態庫

1.4.1 導出內聯函數

VC++編譯生成動態鏈接庫時,允許導出內聯函數。要點就是使用__declspec(dllexport),如:

1.cpp

__declspec(dllexport) __inline int Test()

{

return 1;

}

2.cpp

__declspec(dllexport) __inline int Test()

{

return 2;

}

說明:

1、導出的都是內聯函數的外聯版本,導出前會外聯單選。所以上面代碼導出的Test函數有可能返回1也有可能返回2

2、僅僅在模塊定義文件(*.DEF)裏導出內聯函數是不夠的,必須使用__declspec(dllexport)

1.4.2 導入內聯函數

根據1.cpp2.cpp生成的Test.dll將導出函數Test。客戶程序可以調用此函數,如下所示:

#pragma comment(lib,"Test.lib")

 

__declspec(dllimport) __inline int Test()

{

return 3;

}

 

int main()

{

Test();

int(*pFunc)() = &Test;

(*pFunc)();

return 0;

}

這裏Test函數有兩個版本:內聯版本——返回3的版本;導入版本——該程序導入的Test.dll內的Test函數。

__declspec(dllimport)有兩個作用:

1、禁止內聯函數生成外聯代碼;

2、需要外聯代碼的時候使用內聯函數的導入版本。

現在分析上述代碼的行爲:

1、內聯被啓用時,main函數裏的Test();調用的是內聯版本;

2、內聯被禁用時,main函數裏的Test();調用的是外聯版本,即導入版本;

3、不論是否啓用了內聯功能,(*pFunc)();始終調用外聯版本,即導入版本。

1.5 靜態庫

1.5.1 交叉調用外聯函數

所謂交叉調用就是:客戶程序調用靜態庫的外聯函數,或靜態庫調用客戶程序的外聯函數。

如:程序員在不知道靜態庫裏有內聯函數FuncInline的情況下,在客戶程序裏也編寫了內聯函數FuncInline。當靜態庫、客戶程序均不啓用內聯功能時,就會存在外聯單選的問題。單選結果就是選用靜態庫的外聯版本或選用客戶程序的外聯版本,此時客戶程序調用此函數或靜態庫調用此函數就會發生交叉調用現象。

只要靜態庫、客戶程序的內聯函數的參數、實現保持一致,交叉調用並不可怕。但是,因爲靜態庫的源代碼程序員可能是看不到的,因此由交叉調用而產生的BUG是無法絕對避免的。

1.5.2 缺少外聯函數

GSL函數庫裏有函數gsl_complex_rect的聲明及實現代碼,如下所示:

INLINE_DECL gsl_complex gsl_complex_rect (double x, double y);

#ifdef HAVE_INLINE

INLINE_FUN gsl_complex gsl_complex_rect (double x, double y)

{/* return z = x + i y */

gsl_complex z;

GSL_SET_COMPLEX (&z, x, y);

return z;

}

#endif 

這個函數很簡單,就是返回複數

假定生成GSL靜態庫時,定義了宏HAVE_INLINE,即編譯器支持內聯功能,則gsl_complex_rect的聲明及實現代碼如下:

__inline gsl_complex gsl_complex_rect (double x, double y);

__inline gsl_complex gsl_complex_rect (double x, double y)

{/* return z = x + i y */

gsl_complex z;

GSL_SET_COMPLEX (&z, x, y);

return z;

}

在啓用內聯功能的前提下編譯GSL,生成的靜態庫裏將不會有gsl_complex_rect的外聯代碼。

假定調用GSL的客戶程序裏沒有定義宏HAVE_INLINE,則在客戶程序看來gsl_complex_rect的實現代碼將被禁用,同時其聲明代碼如下:

gsl_complex gsl_complex_rect (double x, double y);

假定客戶程序裏調用了函數gsl_complex_rect,那麼在連接客戶程序時需要查找gsl_complex_rect的二進制代碼。可惜客戶程序、GSL靜態庫裏均找不到,結果就是連接錯誤。

 

發佈了113 篇原創文章 · 獲贊 46 · 訪問量 19萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章