2017-07-02 47 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