我问这个基本的问题是为了让记录更清楚。参考了this question和its currently accepted answer,这并不令人信服。但是second most voted answer给出了更好的洞察力,但也不完美。
阅读下面的内容时,请尝试区分inline
* 关键字 * 和“内联”* 概念 *。
以下是我的看法:
“内联”概念
这样做是为了保存函数的调用开销。它更类似于宏样式的代码替换。没有什么可争议的。
inline
关键字
感知A
inline
关键字是对编译器的一个 * 请求 *,通常用于较小的函数,以便编译器可以优化它并进行更快的调用。编译器可以随意忽略它。
我对此提出部分异议,理由如下:
1.较大的和/或递归函数不会内联,编译器完全忽略inline
关键字
1.无论是否提到inline
关键字,优化器都会自动内联较小的函数。
很明显,用户对使用关键字inline
进行函数内联没有任何控制权。
感知B
inline
与内联的概念 * 没有任何 * 关系。将inline
放在大型/递归函数之前没有帮助,而较小的函数不需要它来内联。inline
的 * 唯一 * 确定性用途是维护 * 一个定义规则 *。
例如,如果一个函数是用inline
声明的,那么 * 只有 * 以下内容是强制的:
1.即使在多个翻译单元中找到其主体(例如,在多个.cpp
文件中包含该标头),编译器也将仅生成1个定义,并避免多个符号链接器错误。(注意:如果函数的主体不同,那么它就是未定义的行为。)
inline
函数的主体必须在使用它的所有翻译单元中可见/可访问。换句话说,在.h
中声明inline
函数并在 * 任何一个 *.cpp
文件中定义将导致其他.cpp
文件出现“未定义符号链接器错误
判决
我觉得“A”是完全错误的,“B”是完全正确的。
有一些引用标准对此,但我期待一个答案,逻辑上解释,如果这个判决正确与否。
Bjarne Stroustrup的电子邮件回复:
- “几十年来,人们一直承诺编译器/优化器在内联方面比人类更好,或者很快就会更好。这在理论上可能是正确的,但对于优秀的程序员来说,这还没有付诸实践,特别是在整个程序优化不可行的环境中。明智地使用显式内联会有很大的好处。"*
2条答案
按热度按时间3z6pesqy1#
我不确定你的说法:
无论是否提到内联,优化器都会自动“内联”较小的函数......很明显,用户无法使用关键字
inline
控制函数“内联”。我听说编译器可以忽略您的
inline
请求,但我不认为他们完全忽略了它。我查看了Clang和LLVM的Github存储库来找出答案。(谢谢,开源软件!)我发现**
inline
关键字 * 确实 * 使Clang/LLVM更有可能内联函数。寻找
在the Clang repository中搜索单词
inline
会得到标记说明符kw_inline
。看起来Clang使用了一个聪明的基于宏的系统来构建词法分析器和其他与关键字相关的函数,所以没有找到像if (tokenString == "inline") return kw_inline
这样的直接函数。但是在ParseDecl.cpp中,我们看到kw_inline
会导致对DeclSpec::setFunctionSpecInline()
的调用。在该函数中,我们设置一个位,如果它是一个重复的
inline
,则发出警告:在其他地方搜索
FS_inline_specified
,我们看到它是位域中的一个位,并且它被用于getter函数isInlineSpecified()
:搜索
isInlineSpecified()
的调用位置,我们找到了codegen,在这里我们将C++解析树转换为LLVM中间表示:叮当响至LLVM
我们已经完成了C++解析阶段,现在
inline
说明符被转换为语言中立的LLVMFunction
对象的属性,我们从Clang切换到the LLVM repository。搜索
llvm::Attribute::InlineHint
得到方法Inliner::getInlineThreshold(CallSite CS)
(带有一个看起来很可怕的没有花括号的if
块):因此,我们已经从优化级别和其他因素中获得了一个基线内联阈值,但如果它低于全局
HintThreshold
,我们就提高它。(HintThreshold可从命令行设置。)getInlineThreshold()
似乎只有一个调用位置,它是SimpleInliner
的成员:它在其指向
InlineCostAnalysis
示例的成员指针上调用一个虚方法(也称为getInlineCost
)。搜索
::getInlineCost()
来查找类成员的版本,我们发现一个版本是AlwaysInline
的成员--这是一个非标准的但被广泛支持的编译器特性--另一个版本是InlineCostAnalysis
的成员,它在这里使用它的Threshold
参数:CallAnalyzer::analyzeCall()
有200多行代码,它做的是决定函数是否可内联的真正的工作。它考虑了很多因素,但是当我们通读这个方法时,我们看到它的所有计算要么操作Threshold
,要么操作Cost
。最后:但是返回值
ShouldInline
实际上是个用词不当,实际上analyzeCall()
的主要用途是设置CallAnalyzer
对象上的Cost
和Threshold
成员变量,返回值只表示当其他因素覆盖了成本与阈值分析时的情况,如我们在下面看到的:否则,我们返回一个存储
Cost
和Threshold
的对象。所以在大多数情况下我们不会返回一个是或否的结果。搜索继续!
getInlineCost()
的返回值用在哪里?真实的的决定
它在
bool Inliner::shouldInline(CallSite CS)
中找到,另一个大函数,它在一开始就调用getInlineCost()
。getInlineCost
分析了内联函数的 * 内在 * 成本--参数签名、代码长度、递归、分支、链接等--以及函数使用的 * 每个 * 位置的一些聚合信息,另一方面,shouldInline()
将这些信息与函数使用的 * 特定 * 位置的更多数据结合起来。在整个方法中,有对
InlineCost::costDelta()
的调用-它将使用analyzeCall()
计算的InlineCost
的Threshold
值。最后,我们返回一个bool
。做出决定。在Inliner::runOnSCC()
中:InlineCallIfPossible()
基于shouldInline()
的决定进行内联。因此
Threshold
受到了inline
关键字的影响,并最终用于决定是否内联。因此,Perception B有一部分是错误的,因为至少有一个主要的编译器基于
inline
关键字更改了其优化行为。但是,我们也可以看到,
inline
只是一个提示,其他因素可能会超过它。cgyqldqp2#
两者都是正确的。
inline
的使用可能会也可能不会影响编译器内联任何特定函数调用的决定,所以A是正确的--它充当了内联函数调用的非绑定请求,编译器可以自由地忽略它。inline
的语义效果是放松了一个定义规则的限制,允许在多个翻译单元中使用相同的定义,如B中所述。对于许多编译器来说,这是允许内联函数调用所必需的-定义必须在此时可用,并且编译器一次只需要处理一个翻译单元。