4

我一直听说该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);
}

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

但是,如果我在 的定义前添加 inline 关键字funcfunc则会内联到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。

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

我的问题是为什么这个代码片段似乎与现代指南相矛盾,例如 我何时应该为函数/方法编写关键字“内联”?

4

4 回答 4

3

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

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

在这种情况下,看到编译器 NOT inline 让我有点惊讶func。但是,添加statictofunc也会使其内联。很明显,编译器是基于“其他函数可能func也在使用”这一事实来决定的,所以无论如何我们都需要一个副本,并且内联它并没有太大的好处。事实上,如果你将一个函数设为静态,它就是只调用一次,即使函数很大,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

编译器决定内联或不内联的逻辑很复杂,其中一部分是“我们获得了多少,与它占用了多少代码空间” - 调用的开销可能operator<<(ostream& ,const char *)对在这种情况下内联。不幸的是,要理解为什么编译器会做出某个决定并不总是那么容易......

于 2013-09-21T12:11:51.727 回答
2

首先,它不是那么黑或白。关键字唯一的绝对作用inline是抑制 ODR 规则并避免多次定义错误。除此之外,编译器当然可以自由地将关键字作为内联的提示,但它可能会也可能不会这样做。(据我所见,实际上编译器通常会忽略此优化提示,因为大多数人不知道内联的频率或内联的内容,编译器可以做得更好)。它不必忽略提示。

inline其次,调用与关键字内联但并非没有关键字可能还有另一个原因。

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

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

作为一个实验,尝试将函数标记为static而不是inline. 这也意味着编译器不必导出定义,而且很可能这也将导致它决定内联是值得的。

于 2013-09-21T12:01:45.267 回答
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)。

于 2018-12-20T03:48:47.487 回答
0

你一直听到的是错误的,或者应该是错误的。该标准明确规定了以下意图inline告诉编译器如果编译器可以内联生成此代码会更好。直到编译器可以比程序员更好地判断何时需要内联,它应该考虑“提示”。也许有一天,inline会变得与此无关(就像register已经成为),但我们离那里还很远。

话虽如此,我很惊讶 g++ 在你的情况下没有内联。g++ 通常对内联相当激进,即使函数没有被标记为inline. 也许它只是认为由于该函数不在循环中,因此不值得费心。

于 2013-09-21T11:56:26.413 回答