-
inline關鍵字在GCC參考文檔中僅有對其使用在函數定義(Definition)上的描述,而沒有提到其是否能用于函數聲明(Declare)。
從inline的作用來看,其放置于函數聲明中應當也是毫無作用的:inline只會影響函數在translation unit(可以簡單理解為C源碼文件)內的編譯行為,只要超出了這個范圍inline屬性就沒有任何作用了。所以inline關鍵字不應該出現在函數聲明中,沒有任何作用不說,有時還可能造成編譯錯誤(在包含了sys/compiler.h的情況下,聲明中出現inline關鍵字的部分通常無法編譯通過);
-
inline關鍵字僅僅是建議編譯器做內聯展開處理,而不是強制。在gcc編譯器中,如果編譯優化設置為-O0,即使是inline函數也不會被內聯展開,除非設置了強制內聯(
__attribute__((always_inline))
)屬性。
1.?GCC的inline
gcc對C語言的inline做了自己的擴展,其行為與C99標準中的inline有較大的不同。
1.1.?static inline
GCC的static inline定義很容易理解:你可以把它認為是一個static的函數,加上了inline的屬性。這個函數大部分表現和普通的static函數一樣,只不過在調用這種函數的時候,gcc會在其調用處將其匯編碼展開編譯而不為這個函數生成獨立的匯編碼。除了以下幾種情況外:
-
函數的地址被使用的時候。如通過函數指針對函數進行了間接調用。這種情況下就不得不為static inline函數生成獨立的匯編碼,否則它沒有自己的地址。
-
其他一些無法展開的情況,比如函數本身有遞歸調用自身的行為等。
static inline函數和static函數一樣,其定義的范圍是local的,即可以在程序內有多個同名的定義(只要不位于同一個文件內即可)。
? | 注意 |
---|---|
gcc的static inline的表現行為和C99標準的static inline是一致的。所以這種定義可以放心使用而沒有兼容性問題。 要點:
|
1.2.?inline
相對于C99的inline來說,GCC的inline更容易理解:可以認為它是一個普通全局函數加上了inline的屬性。即在其定義所在文件內,它的表現和static inline一致:在能展開的時候會被內聯展開編譯。但是為了能夠在文件外調用它,gcc一定會為它生成一份獨立的匯編碼,以便在外部進行調用。即從文件外部看來,它和一個普通的extern的函數無異。舉個例子:
foo.c:/* 這里定義了一個inline的函數foo() */ inline foo() {...; <- 編譯器會像非inline函數一樣為foo()生成獨立的匯編碼 }void func1() {foo(); <- 同文件內foo()可能被編譯器內聯展開編譯而不是直接call上面生成的匯編碼 }
而在另一個文件里調用foo()的時候,則直接call的是上面文件內生成的匯編碼:
bar.c:extern foo(); <- 聲明foo(),注意不能在聲明內帶inline關鍵字void func2() {foo(); <- 這里就是直接call在foo.c內為foo()函數生成的匯編碼了 }
? | 重要 |
---|---|
雖然gcc的inline函數的行為很好理解,但是它和C99的inline是有很大差別的。請注意看后面對C99 inline的描述(第?2.2?節 “inline”),以及如何以兼顧GCC和C99的方式使用inline函數。 要點:
|
1.3.?extern inline
GCC的static inline和inline都很好理解:看起來都像是對普通函數添加了可內聯的屬性。但是這個extern inline就千萬不能想當然地理解成就是一個extern的函數+inline屬性了。實際上gcc的extern inline十分古怪:一個extern inline的函數只會被內聯進去,而絕對不會生成獨立的匯編碼!即使是通過指針應用或者是遞歸調用也不會讓編譯器為它生成匯編碼,在這種時候對此函數的調用會被處理成一個外部引用。另外,extern inline的函數允許和外部函數重名,即在存在一個外部定義的全局庫函數的情況下,再定義一個同名的extern inline函數也是合法的。以下用例子具體說明一下extern inline的特點:
foo.c:extern inline int foo(int a) {return (-a); }void func1() {...;a = foo(a); ①p_foo = foo; ②b = p_foo(b); ③ }
在這個文件內,gcc不會生成foo函數的匯編碼。在func1中的調用點①,編譯器會將上面定義的foo函數在這里內聯展開編譯,其表現類似于普通inline函數。因為這樣的調用是能夠進行內聯處理的。而在②處,引用了foo函數的地址。但是注意:編譯器是絕對不會為extern inline函數生成獨立匯編碼的!所以在這種非要個函數地址不可的情況下,編譯器不得不將其處理為外部引用,在鏈接的時候鏈接到外部的foo函數去(填寫外部函數的地址)。這時如果外部沒有再定義全局的foo函數的話就會在鏈接時產生foo函數未定義的錯誤。
假設在另一個文件里面也定義了一個全局函數foo:
foo2.c:int foo(int a) {return (a); }
那么在上面那個例子里面,后面一個對foo函數地址的引用就會在鏈接時被指到這個foo2.c中定義的foo函數去。也就是說:①調用foo函數的結果是a=-a,因為其內聯了foo.c內的foo函數;而③調用的結果則是b=b,因為其實際上調用的是foo2.c里面的foo函數!
extern inline的用法很奇怪也很少見,但是還是有其實用價值的。第一:它可以表現得像宏一樣,可以在文件內用extern inline版本的定義取代外部定義的庫函數(前提是文件內對其的調用不能出現無法內聯的情況);第二:它可以讓一個庫函數在能夠被內聯的時候盡量被內聯使用。舉個例子:
在一個庫函數的c文件內,定義一個普通版本的庫函數libfunc:
lib.c:void libfunc() {...; }
然后再在其頭文件內,定義(注意不是聲明!)一個實現相同的exterin inline的版本:
lib.h:extern inline libfunc() {...; }
那么在別的文件要使用這個庫函數的時候,只要include了lib.h,在能內聯展開的地方,編譯器都會使用頭文件內extern inline的版本來展開。而在無法展開的時候(函數指針引用等情況),編譯器就會引用lib.c中的那個獨立編譯的普通版本。即看起來似乎是個可以在外部被內聯的函數一樣,所以這應該是gcc的extern inline意義的由來。
但是注意這樣的使用是有代價的:c文件中的全局函數的實現必須和頭文件內extern inline版本的實現完全相同。否則就會出現前面所舉例子中直接內聯和間接調用時函數表現不一致的問題。
? | 重要 |
---|---|
gcc的extern inline函數的用法相當奇怪,使用的范圍也非常狹窄:幾乎沒有什么情況會需要用它。 在C99中,也沒有關于extern inline這樣的描述,所以不建議大家使用extern inline,除非你明確理解了這種用法的意義并且有充足的理由使用它! 要點:
|
2.?C99的inline
以下主要描述C99的inline與Gcc不同的部分。對于相同的部分請參考GCC inline的說明。
2.1.?static inline
同GCC的static inline(第?1.1?節 “static inline”)。
2.2.?inline
C99的inline的使用相當令人費解。當一個定義為inline的函數沒有被聲明為extern的時候,其表現有點類似于gcc中extern 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() {...; } 這樣無論是用gcc的inline規則還是C99的,都能得到完全相同的結果:foo函數會在foo.c文件內被內聯使用,而在外部又可以像普通全局函數一樣直接調用。 ? |
2.3.?extern inline
C99沒有見到extern inline的用法。
?