1. 程式人生 > >linux中noinline和inline關鍵字解析

linux中noinline和inline關鍵字解析

 noinline關鍵字用來通知編譯器不要內聯這個函式。

【賽迪網訊】inline屬性在使用的時候,要注意以下兩點:inline關鍵字在GCC參考文件中僅有對其使用在函式定義(Definition)上的描述,而沒有提到其是否能用於函式宣告(Declare)。

inline的作用來看,其放置於函式宣告中應當也是毫無作用的:inline只會影響函式在translationunit(可以簡單理解為C原始碼檔案)內的編譯行為,只要超出了這個範圍inline屬性就沒有任何作用了。所以inline關鍵字不應該出現在函式宣告中,沒有任何作用不說,有時還可能造成編譯錯誤(在包含了sys/compiler.h

的情況下,宣告中出現inline關鍵字的部分通常無法編譯通過);

inline關鍵字僅僅是建議編譯器做內聯展開處理,而不是強制。在gcc編譯器中,如果編譯優化設定為-O0,即使是inline函式也不會被內聯展開,除非設定了強制內聯(__attribute__((always_inline)))屬性。

1. GCCinline

gccC語言的inline做了自己的擴充套件,其行為與C99標準中的inline有較大的不同。

1.1. static inline

GCCstaticinline定義很容易理解:你可以把它認為是一個static的函式,加上了inline的屬性。這個函式大部分表現和普通的static

函式一樣,只不過在呼叫這種函式的時候,gcc會在其呼叫處將其彙編碼展開編譯不為這個函式生成獨立的彙編碼。除了以下幾種情況外:

函式的地址被使用的時候。如通過函式指標對函式進行了間接呼叫。這種情況下就不得不為staticinline函式生成獨立的彙編碼,否則它沒有自己的地址。

其他一些無法展開的情況,比如函式本身有遞迴呼叫自身的行為等。

staticinline函式和static函式一樣,其定義的範圍是local的,即可以在程式內有多個同名的定義(只要不位於同一個檔案內即可)。

注意:gccstaticinline的表現行為和C99標準的staticinline是一致的。所以這種定義可以放心使用而沒有相容性問題。

要點: gccstaticinline相對於static函式來說只是在呼叫時建議編譯器進行內聯展開;gcc不會特意為staticinline函式生成獨立的彙編碼,除非出現了必須生成不可的情況(如通過函式指標呼叫和遞迴呼叫);gccstaticinline函式僅能作用於檔案範圍內。

1.2. inline

相對於C99inline來說,GCCinline更容易理解:可以認為它是一個普通全域性函式加上了inline的屬性。即在其定義所在檔案內,它的表現和staticinline一致:在能展開的時候會被內聯展開編譯。但是為了能夠在檔案外呼叫它,gcc一定會為它生成一份獨立的彙編碼,以便在外部進行呼叫。即從檔案外部看來,它和一個普通的extern的函式無異。舉個例子:

foo.c:/*這裡定義了一個inline的函式foo()*/ inline foo() {編譯器會像非inline函式一樣為foo()生成獨立的彙編碼}void func1() { foo(); 同文件內foo()可能被編譯器內聯展開編譯而不是直接call上面生成的彙編碼}而在另一個檔案裡呼叫foo()的時候,則直接call的是上面檔案內生成的彙編碼:bar.c:extern foo(); 宣告foo(),注意不能在宣告內帶inline關鍵字voidfunc2() { foo(); 這裡就是直接callfoo.c內為foo()函式生成的彙編碼了

雖然gccinline函式的行為很好理解,但是它和C99inline是有很大差別的。請注意看後面對C99inline的描述(第2.2 節“inline”),以及如何以兼顧GCCC99的方式使用inline函式。

要點: gccinline函式相對於普通extern函式來說只是在同一個檔案內呼叫時建議編譯器進行內聯展開;gcc一定會為inline函式生成一份獨立的彙編碼,以便其在本檔案之外被呼叫。在別的檔案內看來,這個inline函式和普通的extern函式無異;gccinline函式是全域性性的:在檔案內可以作為一個行內函數被內聯展開,而在檔案外可以呼叫它。

1.3. extern inline

GCCstaticinlineinline都很好理解:看起來都像是對普通函式添加了可內聯的屬性。但是這個externinline就千萬不能想當然地理解成就是一個extern的函式+inline屬性了。實際上gccexterninline十分古怪:一個externinline的函式只會被內聯進去,而絕對不會生成獨立的彙編碼!即使是通過指標應用或者是遞迴呼叫也不會讓編譯器為它生成彙編碼,在這種時候對此函式的呼叫會被處理成一個外部引用。另外,externinline的函式允許和外部函式重名,即在存在一個外部定義的全域性庫函式的情況下,再定義一個同名的externinline函式也是合法的。以下用例子具體說明一下externinline的特點:

foo.c:

externinline

intfoo(int a)

{

return (-a);

}

voidfunc1()

{

...;

a= foo(a);

p_foo= foo;

b= p_foo(b);

}

在這個檔案內,gcc不會生成foo函式的彙編碼。在func1中的呼叫點①,編譯器會將上面定義的foo函式在這裡內聯展開編譯,其表現類似於普通inline函式。因為這樣的呼叫是能夠進行內聯處理的。而在②處,引用了foo函式的地址。但是注意:編譯器是絕對不會為externinline函式生成獨立彙編碼的!所以在這種非要個函式地址不可的情況下,編譯器不得不將其處理為外部引用,在連結的時候連結到外部的foo函式去(填寫外部函式的地址)。這時如果外部沒有再定義全域性的foo函式的話就會在連結時產生foo函式未定義的錯誤。

假設在另一個檔案裡面也定義了一個全域性函式foo

foo2.c:

intfoo(int a)

{

return (a);

}

那麼在上面那個例子裡面,後面一個對foo函式地址的引用就會在連結時被指到這個foo2.c中定義的foo函式去。也就是說:①呼叫foo函式的結果是a=-a,因為其內聯了foo.c內的foo函式;而③呼叫的結果則是b=b,因為其實際上呼叫的是foo2.c裡面的foo函式!

externinline的用法很奇怪也很少見,但是還是有其實用價值的。第一:它可以表現得像巨集一樣,可以在檔案內用externinline版本的定義取代外部定義的庫函式(前提是檔案內對其的呼叫不能出現無法內聯的情況);

第二:它可以讓一個庫函式在能夠被內聯的時候儘量被內聯使用。舉個例子:

在一個庫函式的c檔案內,定義一個普通版本的庫函式libfunc

lib.c:

voidlibfunc()

{

...;

}

然後再在其標頭檔案內,定義(注意不是宣告!)

一個實現相同的exterininline的版本:

lib.h:

externinline libfunc()

{

...;

}

那麼在別的檔案要使用這個庫函式的時候,只要includelib.h,在能內聯展開的地方,編譯器都會使用標頭檔案內externinline的版本來展開。而在無法展開的時候(函式指標引用等情況),編譯器就會引用lib.c中的那個獨立編譯的普通版本。即看起來似乎是個可以在外部被內聯的函式一樣,所以這應該是gccexterninline意義的由來。

但是注意這樣的使用是有代價的:c檔案中的全域性函式的實現必須和標頭檔案內externinline版本的實現完全相同。否則就會出現前面所舉例子中直接內聯和間接呼叫時函式表現不一致的問題。gccexterninline函式的用法相當奇怪,使用的範圍也非常狹窄:幾乎沒有什麼情況會需要用它。

C99中,也沒有關於externinline這樣的描述,所以不建議大家使用externinline,除非你明確理解了這種用法的意義並且有充足的理由使用它!

要點: gcc絕對不會為externinline的函式生成獨立彙編碼 ;externinline函式允許和全域性函式重名,可以在檔案範圍內替代外部定義的全域性函式;externinline函式的應用範圍十分狹窄,而且行為比較奇怪,不建議使用

2. C99inline

以下主要描述C99inlineGcc不同的部分。對於相同的部分請參考GCCinline的說明。

2.1. static inline

GCCstaticinline(第1.1 節“static inline”)。

2.2. inline

C99inline的使用相當令人費解。當一個定義為inline的函式沒有被宣告為extern的時候,其表現有點類似於gccexterninline那樣(C99裡面這段描述有點晦澀,原文如下):

If all of the file scope declarationsfor a function in a translation unit include the inline functionspecifier without extern, then the definition in that translationunit is an inline definition. An inline definition does not providean external definition for the function, and does not forbid anexternal definition in another translation unit. An inline definitionprovides an alternative to an external definition, which a translatormay use to implement any call to the function in the same translationunit. It is unspecified whether a call to the function uses theinline definition or the external definition.

即如果一個inline函式在檔案範圍內沒有被宣告為extern的話,這個函式在檔案內的表現就和gccexterninline相似:在本檔案內呼叫時允許編譯器使用本檔案內定義的這個內聯版本,但同時也允許外部存在同名的全域性函式。只是比較奇怪的是C99居然沒有指定編譯器是否必須在本檔案內使用這個inline的版本而是讓編譯器廠家自己來決定,相當模糊的定義。

如果在檔案內把這個inline函式宣告為extern,則這個inline函式的表現就和gccinline一致了:這個函式即成為一個“externaldefinition”(可以簡單理解為全域性函式):可以在外部被呼叫,並且在程式內僅能存在一個這樣名字的定義。

下面舉例說明C99inline的特性:

inlinedouble fahr(double t)

{

return (9.0 * t) / 5.0 + 32.0;

}

inlinedouble cels(double t)

{

return (5.0 * (t - 32.0)) / 9.0;

}

externdouble fahr(double);

doubleconvert(int is_fahr, double temp)

{

returnis_fahr ? cels(temp) : fahr(temp);

}

在上面這個例子裡,函式fahr是個全域性函式:因為在①處將fahr宣告為extern,因此在②處呼叫fahr的時候使用的一定是這個檔案內所定義的版本(只不過編譯器可以將這裡的呼叫進行內聯展開)。在檔案外部也可以呼叫這個函式(說明像gccinline一樣,編譯器在這種情況下會為fahr生成獨立的彙編碼)。

cels函式因為沒有在檔案範圍內被宣告為extern,因此它就是前面所說的“inlinedefinition”,這時候它實際上僅能作用於本檔案範圍(就像一個static的函式一樣),外部也可能存在一個名字也為cels的同名全域性函式。在②處呼叫cels的時候編譯器可能選擇用本檔案內的inline版本,也有可能跑去呼叫外部定義的cels函式(C99沒有規定此時的行為,不過編譯器肯定都會盡量使用檔案內定義的inline版本,要不然inline函式就沒有存在的意義了)。從這裡的表現上看C99中未被宣告為externinline函式已經和gccexterninline十分相似了:本檔案內的inline函式可以作為外部庫函式的替代。

C99標準中的inline函式行為定義的比較模糊,並且inline函式有沒有在檔案範圍內被宣告為extern的其表現有本質不同。如果和gccinline函式比較的話,一個被宣告為externinline函式基本等價於GCC的普通inline函式;而一個沒有被宣告為externinline函式基本等價於GCCexterninline函式。

因為C99inline函式如此古怪,所以在使用的時候,建議為所有的inline函式都在標頭檔案中建立extern的宣告:

foo.h:

externfoo();

而在定義inline函式的c檔案內include這個標頭檔案:

foo.c:

#include"foo.h"

inlinevoid foo()

{

...;

}

這樣無論是用gccinline規則還是C99的,都能得到完全相同的結果:foo函式會在foo.c檔案內被內聯使用,而在外部又可以像普通全域性函式一樣直接呼叫。