天天看點

gcc 中對内聯函數的支援

大家都知道,在程式中,通過把一個函數聲明為内聯(inline)函數,就可以讓gcc把函數的代碼內建(嵌入)到調用該函數的代碼中去。這樣處理可以去掉函數調用時進入/退出時間開銷,進而肯定能夠加快執行速度。是以把一個函數聲明為内聯函數的主要目的就是能夠盡量快速地執行函數體。

在gcc中,如果内聯函數中有常數值,那麼在編譯期間gcc就可能用它來進行一些簡化操作,是以并非所有内聯函數的代碼都會被嵌入到調用代碼處。内聯函數嵌入調用者代碼中的操作是一種優化操作,是以隻有進行優化編譯時才會執行代碼的嵌入。若編譯過程中沒有打開優化選項 "-O",那麼内聯函數的代碼就不會被真正地嵌入到調用者代碼中,而是作為普通函數來處理。

在gcc-4.1的手冊中訓示,需要使用一定的優化級别才能開啟某些優化選項,針對内聯

函數的優化選項主要有:

'-fno-inline' 忽略代碼中的 inline 關鍵字,該選項使編譯器将内聯函數以普通函數對待;等同無優化選項時的處理

'-finline-functions' 編譯器嘗試将'簡單'函數內建到調用代碼處;如果所有對該函數的調用都被替換而內建在調用者代碼中,而且該函數使用static聲明了,則該函數就不再像平常那樣被編譯成彙編代碼。具體什麼方式,需要查詢。必須在-O3選項下才開啟

'-fearly-inlining' 加速編譯 預設可用

'-finline-limit=N' gcc預設限制内聯函數的大小,使用該選項可以控制内聯函數的大小;預設值是600,可以設定如下幾個值:

max-inline-insns-single N/2

max-inline-insns-auto N/2

min-inline-insns 130 or N/4

max-inline-insns-rtl N

'-fkeep-inline-functions' 将聲明為static以及inline的函數放進目标檔案中,即使所有對該函數的調用都被內建在調用者代碼中;該選項不影響使用extern inline聲明的内聯函數,該聲明屬于GNU c擴充。

聲明一個函數為内聯函數的方法:

inline int func(int a)

{

(a)++;

}

函數中的某些語句用法可能會使得内聯函數的替換操作無法正常進行,或者不适合進行替換操作。例如使用了可變參數,記憶體配置設定函數 malloc(),可變長度資料類型變量,非局部 goto語句以及遞歸函數。編譯時可以使用選項 -Winline 讓 gcc 對标志成 inline 但不能被替換的函數給出警告資訊以及不能替換的原因。如下面例子,它使用了可變長度資料類型變量作為參數:

int c = 4;

char p[c]; / 可變長度數組 /

/*

  • 測試函數: 使用 gcc -Winline ... 來提示資訊

    */

    int main(void)

    int d = 1;

    func(&d);

    return 0;

假定存儲檔案為inline.c, gcc -O -Winline -c -o inline.o inline.c 來編譯,這裡 -O 選項必須打開,否則将沒有警告資訊輸出,因為該函數會被當作普通函數來處理;警告資訊如下:

inline.c: In function ‘func’:

inline.c:2: warning: function ‘func’ can never be inlined because it uses alloca (override using the always_inline attribute)

inline.c: In function ‘main’:

inline.c:2: warning: inlining failed in call to ‘func’: function not inlinable

inline.c:11: warning: called from here

可以看出它覆寫了 always_inline 屬性,其它無法内聯的的用法大家可以自己編寫代碼測試。

當在一個函數定義中既使用 inline 又使用 static 關鍵字時,那麼如果所有對該内聯函數的調用都被替換而內建在調用者代碼中,并且程式中沒有引用過該内聯函數的位址,則該内聯函數自身的彙編代碼就不會被引用。這時,除非在編譯過程中使用選項'-fkeep-inline-functions',否則 gcc 就不會再為該内聯函數自身生成實際的彙編代碼。由于某些原因,一些對内聯函數的調用并不能被內建到函數中去。特别是在内聯函數定義之前的調用語句是不會被替換內建的,并且也都不能是遞歸定義的函數。如果存在一個不能被替換內建的調用,那麼内聯函數就會像普通函數一樣被編譯成彙編代碼,對于程式中有引用該内聯函數的位址的處理同樣無法內建。

對于上面這句話的了解,同樣我們可以使用上面的 inline.c 函數來測試,當沒有加上 static 關鍵字的時候,可以使用

gcc -O -Winline -S -o inline.s inline.c

來生成彙程式設計式,可以看到 func 内聯函數在彙編代碼中同樣被生成彙編代碼而且被聲明為函數;修改 inline.c 增加 static 關鍵字,gcc -O -Winline -S -o inline2.s inline.c 比較兩個檔案可以看到 inline2.s 中隻有 main 符号,func 的代碼直接被內建到 main 中了,此時如果想産生和沒有加 static時的效果,編譯時就要加上選項 '-fkeep-inline-functions';但是在 C++ 中,該選項會生成一個弱".weak"函數,也就是單獨的彙編代碼,若不加該選項,内聯函數語義等同于 ISO C99 的語義,也就是都不單獨生成彙編代碼。

/* 不生成單獨的彙編代碼的版本,或者替換 static 為 extern /

static inline int func(int a)

/ int c = 4;

char p[c]; / 可變長度數組 */

/

如果内聯函數定義時沒有使用 static,那麼 gcc 就會假設其它程式檔案中也對這個函數有調用。因為一個全局符号隻能被定義一次,是以該函數就不能在其它源檔案中再進行定義。是以這裡對内聯函數的調用就不能被替換內建。是以,一個非靜态的内聯函數總是會被編譯出自己的彙編代碼來。另外,ISO 标準 C99 中對不使用 static 關鍵字的内聯函數定義等同于這裡使用 static

繼續閱讀