C ++`inline`关键字和编译器优化

时间:2013-09-21 11:50:50

标签: c++ gcc

我一直听说inline关键字不再用作现代编译器的提示,但用于避免多源项目中的多重定义错误。

但今天我遇到了一个编译器服从关键字的例子。

没有inline关键字,以下代码

#include <iostream>

using namespace std;

void func(const int x){
    if(x > 3)    
        cout << "HAHA\n";
    else
        cout << "KKK\n";
}

int main(){
    func(5);
}

使用命令g++ -O3 -S a.cpp生成汇编代码,func未内联。

但是,如果我在func的定义前面添加了内联关键字,func会被内联到main

生成的汇编代码的一部分是

.LC0:
    .string "HAHA\n"
.LC1:
.string "KKK\n"
.text
.p2align 4,,15
.globl  _Z4funci
.type   _Z4funci, @function
_Z4funci:
.LFB975:
    .cfi_startproc
    cmpl    $3, %edi
    jg  .L6
    movl    $4, %edx
    movl    $.LC1, %esi
    movl    $_ZSt4cout, %edi
    jmp _ZSt16__ostream_insertIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_PKS3_l
    .p2align 4,,10
    .p2align 3

main:
.LFB976:
    .cfi_startproc
    subq    $8, %rsp
    .cfi_def_cfa_offset 16
    movl    $5, %edi
    call    _Z4funci
    xorl    %eax, %eax
    addq    $8, %rsp
    .cfi_def_cfa_offset 8
    ret
    .cfi_endproc

我的编译器是gcc 4.8.1 / x86-64。

我怀疑该功能可以在链接过程中内联,但我不确定是否会发生,如果是,我怎么知道?

我的问题是为什么这段代码片段似乎与现代指南相矛盾 When should I write the keyword 'inline' for a function/method?

4 个答案:

答案 0 :(得分:3)

inline关键字有多种效果。其中一个是向编译器提示您希望函数内联 - 但是,这并不意味着编译器必须内联它[在几个编译器中有一个扩展名称“无论如何都内联这个,如果有的话”可能的“,例如MS的__forceinline和gcc的__attribute__(always_inline)]。

如果函数内联,inline关键字还允许您拥有具有相同名称的函数的多个实例,而不会出现“同一函数的多个定义”的错误。 [但每次功能必须是相同的来源]。

在这种情况下,看到编译器不内联func,我有点惊讶。但是,将static添加到func也会使其内联。很明显,编译器根据“其他一些函数也可能正在使用func这一事实来决定这一点,所以我们无论如何都需要一个副本,并且内联它并没有太大的收获。事实上,如果你做了一个函数static,它只被调用一次,即使函数非常大,gcc / g ++几乎肯定会内联它。

如果您希望编译器内联某些东西,添加inline永远不会受到伤害。但是,在许多情况下,编译器将以任何一种方式做出正确的选择。例如,如果我将代码更改为:

const char* func(const int x){
    if(x > 3)    
        return "HAHA\n";
    else
        return "KKK\n";
}

int main(){
    cout << func(5);
}

它会内联return "HAHA\n";左侧的func部分。

编译器决定内联或不内联的逻辑是复杂的,其中一部分是“我们获得了多少,相比它占用了多少代码空间” - 这可能是调用{{在这种情况下,1}}对于内联器来说太过分了。不幸的是,理解为什么编译器做出某种决定并不总是那么容易......

答案 1 :(得分:2)

首先,它不是那么黑或白。 inline关键字的唯一绝对效果是抑制ODR规则并避免多个定义错误。除此之外,编译器当然可以自由地将关键字作为关于内联的提示,但它可能会也可能不会。 (从我所看到的,在实践中,编译器通常会忽略这个优化提示,因为大多数人都不知道内联的频率,或者内联的内容,编译器可以更好地完成它)。但它没有 忽略提示。

其次,可能还有另一个原因,即使用inline关键字来内联,但不是没有。

如果没有inline关键字,则必须导出函数定义,因为另一个TU可能需要链接到它。因为我们必须导出函数定义,所以代码已经存在,并且内联调用只意味着你有效地复制了函数体。更多的总代码,更大的可执行文件大小,更高的指令缓存位置。

但是使用inline关键字,编译器不必导出函数定义,因此它可以内联调用并完全删除原始定义。然后总代码大小不会增加(而不是生成函数定义和调用它,我们只是将函数体移动到调用站点)。

作为实验,请尝试将该功能标记为static而不是inline。这也意味着编译器不必导出定义,很可能也会导致它决定内联是否值得。

答案 2 :(得分:1)

今天(在2018年),inline属性仍用于优化。即使在现代编译器中。

至少在开源编译器GCC和Clang中,他们会忽略它而是纯粹依赖于自己的成本模型的说法是不正确的。西蒙·布兰德(Simon Brand)撰写了一篇不错的博客文章(Do compilers take inline as a hint?),在其中他通过查看编译器的源代码对它进行了揭穿。

但这并不是这些编译器会盲目地遵循程序员的提示。如果他们有足够的证据表明这会损害性能,那么他们将否决您。

有一些特定于供应商的扩展将强制内联,即使编译器认为这是一个坏主意。例如,在Visual Studio中,它称为__forceinline

  

__forceinline关键字将覆盖成本/收益分析,而是取决于程序员的判断。使用__forceinline时要小心。不加选择地使用__forceinline可能会导致更大的代码,而只获得少量的性能提升,或者在某些情况下甚至导致性能下降(例如,由于较大的可执行文件的分页增加)。

GCC和Clang称之为inline __attribute__ ((__always_inline__))

通常,建议使用决定信任编译器,特别是如果可以使用profile-guided optimization。使用强制内联的高质量代码库的一个显着例外是Boost(查找BOOST_FORCEINLINE)。

答案 3 :(得分:0)

你听到的是假的,或应该是。标准清楚 指定inline intent :告诉编译器它会是什么 如果编译器可以内联生成此代码,则更可取。直到 编译器可以比判断何时的程序员做得更好 内联是必要的,它应该考虑“提示”。也许 有一天,inline将与此无关(就像register一样) 但是,我们离那里很远。

话虽如此,我很惊讶g ++并没有在你的内容中内嵌 案件。 g ++通常对内联非常积极,即使是 函数未标记为inline。也许它只是认为自从 功能不在循环中,不值得烦。