2017-07-02 49 views
5

從網上的一些話我知道GCC足夠聰明,可以決定是否內聯函數。 inline關鍵字只是一個提示:
GCC可能內聯常用函數,並且可能不內聯內聯函數爲什麼gcc沒有爲這個函數決定內聯或者不內聯?

但在我的項目這個功能:

struct vb_pos{ 
    union{ 
     struct{ 
      int offset; 
      int l; 
     }; 
     unsigned long long g_offset;  
    }; 
}; 
static inline void vi_write_vtail_smart(struct vi *vi){ 
    struct vb_pos *vhead, *vtail, *cursor; 
    vhead = &vi->v_head;      
    vtail = &vi->v_tail; 
    cursor = &vi->cursor; 

    int curoff = vi->curr - vi->lines[vi->currl].buf; 
    cursor->offset = curoff;  

    if(cursor->g_offset >= vhead->g_offset){ 
     *vtail = *cursor; 
    } 
    else{ 
     *vtail = *vhead; 
     *vhead = *cursor; 
    } 
} 

編譯-02。
我檢查了彙編代碼,知道這個函數按預期內聯了。
但是,當我刪除它的inline修飾符並重新編譯時,我發現它並不是內聯的內聯。它的功能體出現在最終的二進制文件:

0000000000000000 <vi_write_vtail_smart>: 
     0:  48 63 47 14    movslq 0x14(%rdi),%rax 
     4:  48 8b 17    mov (%rdi),%rdx 
     7:  48 8d 04 40    lea (%rax,%rax,2),%rax 
     b:  48 8d 04 c2    lea (%rdx,%rax,8),%rax 
     f:  48 8b 57 18    mov 0x18(%rdi),%rdx 
     13:  48 2b 10    sub (%rax),%rdx 
     16:  89 57 10    mov %edx,0x10(%rdi) 
     19:  48 8b 47 10    mov 0x10(%rdi),%rax 
     1d:  48 3b 47 38    cmp 0x38(%rdi),%rax 
     21:  73 0d     jae 30 <vi_write_vtail_smart+0x30> 
     23:  48 8b 57 38    mov 0x38(%rdi),%rdx 
     27:  48 89 47 38    mov %rax,0x38(%rdi) 
     2b:  48 89 57 40    mov %rdx,0x40(%rdi) 
     2f:  c3      retq 
     30:  48 89 47 40    mov %rax,0x40(%rdi) 
     34:  c3      retq 

我想知道,因爲GCC是足夠聰明,爲什麼沒有它有自己的決定?爲什麼它在我指定時以內聯方式執行,而不是當我不指定時執行?

因爲他沒有找到足夠的線索做出有力的決定?或者,因爲他已經做出了決定,他的決定是:內在與內在沒有太大的區別,並且自從你問我以後,我就會爲你內聯;否則,我把它作爲一個共同的功能。

我想知道真正的原因。
如果是第一種情況,我認爲我們可能需要在這篇文章開頭重新考慮觀點(網上非常流行)----至少,GCC沒有他們所說的那麼聰明,並且關鍵詞內聯並不像他們所說的那樣毫無用處。

在文章結束,我要添加更多的描述爲上面的代碼段的上下文:

1,I原本想vi_write_vtail_smart()到內聯到功能A()B(),其被導出爲庫API和兩者都會經常被用戶調用。

2,A()B()vi_write_vtail_smart()在同一個文件中。

3,vi_write_vtail_smart()僅用於A()B(),沒有其他地方。

4,A()的函數體大小約爲450字節,與B()類似。

5,A()B()基本上是普通的機器碼,沒有涉及大循環或重計算,只有一個子函數被調用,除了vi_write_vtail_smart()。該子功能位於另一個文件中。

6,我做了一個小測試,我添加了一行return;如果(CURSOR-> g_offset> = vhead-> g_offset){之前,(我想看看發生了什麼事時,這個功能是足夠小)即:

... 
int curoff = vi->curr - vi->lines[vi->currl].buf; 
cursor->offset = curoff;  

return; 
if(cursor->g_offset >= vhead->g_offset){ 
... 

並編譯沒有inline修改,並檢查了彙編代碼----這時候GCC內聯它和它的函數定義從最終的二進制文件中消失。

7,我的開發環境:
Ubuntu的16.04/64
gcc版本5.4.0 20160609
架構:英特爾X86 IvyBridge的移動

9,編譯標誌(必須在這裏寫一遍,有些人讀取時忽略) -O2 -std = gnu99

+0

不錯的問題,但通過指出確切的GCC版本,目標操作系統和目標架構,您可能會做得更好。 – iehrlich

+3

請注意,「inline」關鍵字仍具有實際含義,與函數是否內聯無關:使用關鍵字,每個翻譯單元可能會出現相同的函數定義,因此可能位於頭文件中。如果沒有關鍵字,函數定義可能只會出現在每個程序中,因此不應該在頭文件中。 – aschepler

+2

因此,如果沒有'inline'關鍵字,GCC *必須*提供一個定義作爲命名符號,以防其他翻譯單元使用它。但是這並不妨礙它在另一個調用它的函數中內聯它。 – aschepler

回答

11

根據GCC文檔,GCC有一個名爲-finline-functions的優化設置。這實際上是使GCC在所有函數上使用其啓發式內聯條件的設置,即使它們未被聲明爲inline。此設置在-O3優化級別啓用。因此,您要讓GCC完全自由地將其啓發式應用於所有功能,則必須至少指定-O3(或明確指定-finline-functions)。

如果沒有-finline-functions GCC通常不嘗試內聯未聲明的函數inline,但有一些明顯的例外:其他一些內聯選項也可能導致非內聯函數獲得內聯。然而,這些選項是在非常特殊的情況

  • -finline-functions-called-once目標是早-O1啓用。只有一次調用的靜態函數被內聯,即使它們沒有被聲明爲inline

  • -finline-small-functions-O2處啓用。如果它導致代碼大小減小,即使該函數未聲明爲inline,它也會觸發內聯。

你的功能顯然不會在-O2級通有源這些特定的內聯過濾器:它是相對較大,(顯然)調用一次以上。出於這個原因,除非您明確要求使用inline關鍵字,否則GCC不會將其視爲內聯-O2。請注意,明確的inline關鍵字基本上就像-finline-functions設置僅針對該特定功能開啓。它會讓GCC爲考慮它用於內聯,但不保證內聯。

同樣,如果您希望GCC完全接管這些決策,您需要-finline-functions-O3。顯式inline關鍵字觸發內聯-O2的事實意味着GCC應該決定將其內聯於-O3,而不管其中是否存在inline

+0

並有'inline __attribute __((always_inline))',**強制** gcc內聯函數(即使在未優化的版本中)。 – geza

+0

我用-O3重新編譯並檢查,gcc確實將其內聯。 – weiweishuo