目錄
第1章 VC++
1.1 inline與__inline
對於VC++而言,inline僅用於內聯C++函數,而__inline可用於內聯C和C++函數。
1.2 啟用內聯
編譯VC++ Debug 版程序時,內聯功能默認是被禁用的。如果需要,可以啟用內聯。以VC++6.0為例,其操作步驟如下:
將Inline function expansion由Disable*更改為Only __inline。如下圖所示:
圖1.1
將Debug info由Program 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被聲明為內聯函數,但是這個編譯單元(編譯前是c或cpp文件,編譯后就是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.obj和2.obj均有FuncInline的外聯版本。Func1和Func2里的代碼FuncInline();究竟會調用哪個obj文件里的FuncInline?答案是連接時的第一個,其它的均被舍棄。此時,Func1和Func2里的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.obj、2.obj里的外聯函數。外聯函數可以單選,但是外聯函數與外部函數是不能單選的。這就造成連接時編譯器無法確定Func1、Func2、Func3究竟要使用外聯函數還是外部函數,從而導致編譯失敗。
1.3.4 外聯並存
使用static inline可以保留一個內聯函數的所有外聯版本,其功能為:修飾內聯函數的外聯版本為static函數。
舉例說明:現在修改1.cpp和2.cpp的inline為static inline。
VC++編譯Debug版(內聯被禁用),此時1.obj和2.obj均有FuncInline的外聯版本,但是它們都是static函數。因此,相同的代碼FuncInline();,在Func1里會調用1.obj里的FuncInline,在Func2里會調用2.obj里的FuncInline。同樣的,Func1和Func2里的pFunc也分別指向1.obj和2.obj里的FuncInline。
顯然:外聯單選時,不會單選static inline函數的外聯版本。
1.3.5 extern inline
根據static inline可以理解extern inline的功能為:修飾內聯函數的外聯版本為extern函數(即外部函數)。事實上,不使用extern關鍵字,外聯函數就是外部函數。因此,VC++里extern inline與inline是沒有任何分別的。
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.cpp和2.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靜態庫里均找不到,結果就是連接錯誤。