C++“内联”关键字和编译器优化

C++ `inline` keyword and compiler optimization

本文关键字:编译器 优化 关键字 内联 C++      更新时间:2023-10-16

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

但是今天我遇到了一个编译器遵守关键字的示例。

如果没有inline关键字,则以下代码

#include <iostream>
using namespace std;
void func(const int x){
    if(x > 3)    
        cout << "HAHAn";
    else
        cout << "KKKn";
}
int main(){
    func(5);
}

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

但是,如果我在func的定义前面添加内联关键字,则func将内联到main中。

生成的汇编代码部分是

.LC0:
    .string "HAHAn"
.LC1:
.string "KKKn"
.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。

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

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

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

inline 关键字还允许您在函数内联时拥有具有相同名称的函数的多个实例,而不会收到"同一函数的多个定义"的错误。[但该函数每次都必须是相同的源]。

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

如果您希望编译器内联某些内容,则添加inline永远不会有什么坏处。但是,在许多情况下,无论哪种方式,编译器都会做出不错的选择。例如,如果我将代码更改为以下内容:

const char* func(const int x){
    if(x > 3)    
        return "HAHAn";
    else
        return "KKKn";
}
int main(){
    cout << func(5);
}

它确实内联了func剩下的return "HAHAn";部分。

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

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

其次,很可能还有另一个原因,为什么调用内嵌了inline关键字,但并非没有。

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

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

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

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

声称他们会忽略它,而是纯粹依赖自己的成本模型是不正确的,至少在开源编译器GCC和Clang中是这样。西蒙·布兰德(Simon Brand)写了一篇很好的博客文章(编译器是否将内联作为提示?),他通过查看编译器的源代码来揭穿它。

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

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

__forceinline关键字覆盖成本/收益分析,而是依赖于程序员的判断。使用__forceinline时要小心。不分青红皂白地使用 __forceinline 可能会导致代码变大,而性能提升很小,或者在某些情况下甚至会导致性能损失(例如,由于增加较大可执行文件的分页)。

海湾合作委员会和Clang称之为inline __attribute__ ((__always_inline__))

通常,建议信任编译器的决策,尤其是在可以使用按配置文件优化的情况下。使用强制内联的高质量代码库的一个值得注意的例外是 Boost(查找 BOOST_FORCEINLINE )。

你一直听到的是假的,或者应该是假的。 标准明确指定inline意图:告诉编译器它将如果编译器可以内联生成此代码,则更可取。 直到编译器可以比程序员更好地判断何时内联是必要的,它应该考虑"提示"。 或总有一天,inline会变得与此无关(就像register一样成为),但我们离那还很远。

话虽如此,我很惊讶 g++ 没有内联在你的箱。 G++ 通常对内联相当积极,即使函数未标记为inline 。 也许它只是想,因为功能没有循环,不值得费心。