我一直听说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?
答案 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
。也许它只是认为自从
功能不在循环中,不值得烦。