inline關鍵字在GCC參考文檔中僅有對其使用在函數定義(Definition)上的描述,而沒有提到其是否能用於函數聲明(Declare).
inline關鍵字不應出現在函數聲明中。
inline關鍵字僅僅是建議編譯器做內聯展開處理,而不是強制。在gcc編譯器中,如果編譯優化設置為O0,即使是inline函數也不會被內聯展開,除非設置了強制內聯(__attribute__((always_inline)))屬性。對於可展開與必須當成函數的情形同時出現,則在展開處需展開,在當成函數調用處則當函數處理.
1.1. static inline
gcc的static inline相對於static函數來說只是在調用時建議編譯器進行內聯展開;gcc不會特意為static inline函數生成獨立的匯編碼,除非出現了必須生成不可的情況(例如函數指針調用和遞歸調用);
I)不展開
函數本身遞歸等;函數的地址被使用時(賦予函數指針)
II)展開
gcc會在其調用處將其匯編碼展開編譯而不為這個函數生成獨立的匯編碼.
1.2. inline
gcc的inline更容易理解:可以認為它是一個普通的全局函數加上了inline的屬性。即在其定義所在文件內,它的行為和static inline一致:在能展開的時候會被內聯展開編譯。但是為了能夠在文件外調用它,gcc一定會為它生成一份獨立的匯編碼,以便在外部進行調用。
example:
/*****foo.c*****/
/*這里定義一個inline的函數foo()*/
inline foo(void)
{
/*編譯器在本文件內對其所調用處進行展開*/
/*但同時也會像非inline函數一樣為foo()生成獨立的匯編碼,以便文件外的函數調用foo();*/
}
void func1()
{
/*同文件內foo()可能被編譯器內聯展開編譯而不是直接call上面生成的匯編碼*/
foo();
}
/*****bar.c*****/
/*而在另一個文件里調用foo()的時候,則直接call的是上面文件內生成的匯編碼:*/
extern foo();//聲明foo(),注意不能在聲明內帶inline關鍵字
void func2()
{
/*這里就會直接call在foo.c內為foo()函數生成的匯編碼了*/
foo();
}
gcc的inline函數相對於普通的extern函數來說只是在同一個文件內調用時建議編譯器進行內聯展開;gcc一定回為inline函數生成一份獨立的匯編碼以供外部文件調用。在其它文件看來,這個inline函數和普通的extern函數無異;gcc的inline函數是全局性的:在文件內可以作為一個內聯函數被內聯展開,而在文件外可以調用它。
gcc的static inline和inline比較容易理解,可以認為是對普通函數添加可內聯的屬性。
1.3. extern inline
gcc的extern inline十分古怪:一個extern inline函數只會被內聯,而絕不會生成獨立的匯編碼!!!如果某處必須將其當成普通的函數,則此時對此函數的調用會被處理成一個外部引用。此外,extern inline 的函數允許和外部函數重名,即在存在一個外部定義的全局庫函數的情況下,再定義一個同名的extern inline函數也是合法的。
example:
/*******foo.c*******/
extern inline int foo(int a)
{
printf("%d\n",-a);
return -a;
}
void func1()
{
......;
a = foo(a); //*******1
p_foo = foo; //*******2
b = p_foo(b); //*******3
}
/*****foo2.c******/
int foo(int a)
{
printf("%d\n",a);
return a;
}
在這個文件內,gcc不會生成foo函數的匯編碼。
在func1中的1處,編譯器會將上面定義的foo函數在這里內聯展開編譯,其行為類似普通的inline函數,因為這樣的調用能夠進行內聯處理。
在func1中的2處應用了名稱為foo的函數的地址,但是由於編譯器絕不會為extern inline函數生成獨立的匯編碼,所以在這種非要取得函數地址的情況下,編譯其只能將其處理為外部引用,在鏈接的時候鏈接到外部的foo函數(填寫外部的函數地址)。這時,如果外部沒有定義全局的foo函數的話鏈接時將產生foo函數位定義的錯誤。由於在func2.c中定義的全局的foo(int)函數,所以對於上面的例子,將會在1處使得a=-a,因為其內聯了foo.c內的foo函數; 在3處使得b=b,因為去實際上調用的是foo2.c里面的foo函數!
extern inline的價值:
第一:其行為可以像宏一樣,可以在文件內用extern inline定義的版本取代外部定義的庫函數(前提是文件內對其的調用不能出現無法內聯的情況);
第二:它可以讓一個庫函數在能夠被內聯時盡可能被內聯使用,example:
在一個庫函數的c文件內,定義一個普通版本的庫函數libfunc:
/*****lib.c*****/
void libfunc(void)
{
....;
}
然后再在其頭文件內,定義(注意不是聲明!)一個實現相同的extern inline的版本:
/*****lib.h*****/
extern inline libfunc(void)
{
....;
}
那么在別的文件要使用這個庫函數的時候,只要include進lib.h,在能內聯展開的地方,編譯器都會使用頭文件內extern inline 的版本來展開。而在無法展開的時候(函數指針引用等情況),編譯器就會引用lib.c中的那個獨立編譯的普通版本。即看起來似乎是個可以在外部被內聯的函數一樣,所以這應該是gcc的extern inline意義的由來。
但是應當注意這樣使用的代價:c文件中的全局函數的實現必須和同文件內extern inline版本的實現完全相同。否則就會出現前面所舉例子中直接內聯和間接調用時函數行為不一致的問題。
gcc絕不會為extern inline的函數生成獨立的匯編碼;extern inline函數允許和全局函數重名,可以在文件范圍內替代外部定義的全局函數;使用extern inline時必須給予文檔注釋!!!!
***************************************************************************************
C99的inline
2.1. static inline(同gcc 的static inline)
2.3. extern inline (無說明)
2.2. inline
如果一個inline函數在文件范圍內沒有被聲明為extern的話,這個函數在文件內的表現就和gcc的extern inline相似:在文件內調用時**允許**編譯器使用文件內定義的這個內聯版本,但同時也**允許**外部存在同名的全局函數。但是C99沒有明確的指出編譯器是否必須在文件內使用這個inline的版本,而是由編譯器的廠家自己來決定。
如果在文件內把這個inline函數聲明為extern,則這個inline函數的行為就和gcc的inline一致了:這個函數即成為一個"external definition"(可以簡單理解為全局函數):可以在外部被調用,並且在程序內僅能存在一個這樣的名字。
舉例說明C99中inline的特性:
inline double fahr(double t)
{
return (9.0 * t) / 5.0 + 32.0;
}
inline double cels(double t)
{
return (5.0 * (t - 32.0)) / 9.0;
}
extern double fahr(double); ①
double convert(int is_fahr, double temp)
{
return is_fahr ? cels(temp) : fahr(temp); ②
}
函數fahr是個全局函數:因為在① 處將fahr聲明為extern,因此在② 處調用fahr的時候使用的一定是這個文件內所定義的版本(只不過編譯器可以將這里的調用進行內聯展開)。在文件外部也可以調用這個函數(說明像gcc 的inline一樣,編譯器在這種情況下會為fahr生成獨立的匯編碼)。
而cels函數因為沒有在文件范圍內被聲明為extern,因此它就是前面所說的 “inline definition”,這時候它實際上僅能作用於本文件范圍(就像一個static的函數一樣),外部也可能存在一個名字也為cels的同名全局函數。 在② 處調用cels的時候編譯器可能選擇用本文件內的inline版本,也有可能跑去調用外部定義的cels函數(C99沒有規定此時的行為,不過編譯器肯定都會盡量使用文件內定義的inline版本,要不然inline函數就沒有存在的意義了)。從這里的表現上看C99中未被聲明為extern的 inline函數已經和gcc的extern inline十分相似了:本文件內的inline函數可以作為外部庫函數的替代。
C99標准中的inline函數行為定義的比較模糊,並且inline函數有沒有在文件范圍內被聲明為extern的其表現有本質不同。
如果和gcc的inline函數比較的話,一個被聲明為extern的inline函數基本等價於GCC 的普通inline函數;而一個沒有被聲明為extern的inline函數基本等價於GCC的extern inline函數。
兼容性:
因為C99的inline函數如此古怪,所以在使用的時候,建議為所有的inline函數都在頭文件中創建extern的聲明:
/*******foo.h********/
extern foo();
而在定義inline函數的c文件內include這個頭文件:
/*******foo.c********/
#include "foo.h"
inline void foo()
{
...;
}