C語言的inline(轉載)

轉之前我用Docbook寫的一篇關於C語言inline關鍵字使用的文章。唉,要是能用docbook直接寫Blog就行了。用得越多發現Docbook這個東西真是越好用啊~~html

   

本文介紹了GCC和C99標準中inline使用上的不一樣之處。inline屬性在使用的時候,要注意如下兩點:ide

  1. inline關鍵字在GCC參考文檔中僅有對其使用在函數定義(Definition)上的描述,而沒有提到其是否能用於函數聲明(Declare)。

inline的做用來看,其放置於函數聲明中應當也是毫無做用的:inline只會影響函數在translation unit(能夠簡單理解爲C源碼文件)內的編譯行爲,只要超出了這個範圍inline屬性就沒有任何做用了。因此inline關鍵字不該該出如今函數聲明中,沒有任何做用不說,有時還可能形成編譯錯誤(在包含了sys/compiler.h的狀況下,聲明中出現inline關鍵字的部分一般沒法編譯經過);函數

  1. inline關鍵字僅僅是建議編譯器作內聯展開處理,而不是強制。在gcc編譯器中,若是編譯優化設置爲-O0,即便是inline函數也不會被內聯展開,除非設置了強制內聯(__attribute__((always_inline)))屬性。

1. GCC的inline優化

gcc對C語言的inline作了本身的擴展,其行爲與C99標準中的inline有較大的不一樣。編碼

1.1. static inlinespa

GCC的static inline定義很容易理解:你能夠把它認爲是一個static的函數,加上了inline的屬性。這個函數大部分表現和普通的static函數同樣,只不過在調用這種函數的時候,gcc會在其調用處將其彙編碼展開編譯而不爲這個函數生成獨立的彙編碼。除了如下幾種狀況外:指針

  • 函數的地址被使用的時候。如經過函數指針對函數進行了間接調用。這種狀況下就不得不爲static inline函數生成獨立的彙編碼,不然它沒有本身的地址。
  • 其餘一些沒法展開的狀況,好比函數自己有遞歸調用自身的行爲等。

static inline函數和static函數同樣,其定義的範圍是local的,便可以在程序內有多個同名的定義(只要不位於同一個文件內便可)。htm

 

注意遞歸

gcc的static inline的表現行爲和C99標準的static inline是一致的。因此這種定義能夠放心使用而沒有兼容性問題。ip

要點:

  • gcc的static inline相對於static函數來講只是在調用時建議編譯器進行內聯展開;
  • gcc不會特地爲static inline函數生成獨立的彙編碼,除非出現了必須生成不可的狀況(如經過函數指針調用和遞歸調用);
  • gcc的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函數。

要點:

  • gcc的inline函數相對於普通extern函數來講只是在同一個文件內調用時建議編譯器進行內聯展開;
  • gcc必定會爲inline函數生成一份獨立的彙編碼,以便其在本文件以外被調用。在別的文件內看來,這個inline函數和普通的extern函數無異;
  • gcc的inline函數是全局性的:在文件內能夠做爲一個內聯函數被內聯展開,而在文件外能夠調用它。

1.3. extern inline

GCC的static inlineinline都很好理解:看起來都像是對普通函數添加了可內聯的屬性。可是這個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函數的地址。可是注意:編譯器是絕對不會爲externinline函數生成獨立彙編碼的!因此在這種非要個函數地址不可的狀況下,編譯器不得不將其處理爲外部引用,在連接的時候連接到外部的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這樣的描述,因此不建議你們使用externinline,除非你明確理解了這種用法的意義而且有充足的理由使用它!

要點:

  • gcc絕對不會爲extern inline的函數生成獨立彙編碼
  • extern inline函數容許和全局函數重名,能夠在文件範圍內替代外部定義的全局函數
  • extern inline函數的應用範圍十分狹窄,並且行爲比較奇怪,不建議使用

2. C99inline

如下主要描述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那樣(C99裏面這段描述有點晦澀,原文以下):

If all of the file scope declarations for a function in a translation unit include the

inline function specifier without extern, then the definition in that translation unit is an inline definition. An inline definition does not provide an external definition for the function, and does not forbid an external definition in another translation unit. An inlinedefinition provides an alternative to an external definition, which a translator may use to implement any call to the function in the same translation unit. It is unspecified whether a call to the function uses the inline definition or the external definition.

即若是一個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的用法。

相關文章
相關標籤/搜索