"内联"关键字vs"内联"概念

iam*_*ind 37 c c++ inline-functions language-lawyer

我问这个基本问题是为了让记录笔直.已经提到这个问题它的目前公认的答案,这是不能令人信服的.然而,第二多投票的答案提供了更好的洞察力,但也不完美.

在阅读下面时,区分inline 关键字和"内联" 概念.

这是我的看法:

内联概念

这样做是为了节省函数的调用开销.它更类似于宏式代码替换.没有什么可争议的.

inline关键字

感知A.

inline关键字是一个请求,以通常用于较小的函数的编译器,以便编译器可以优化它和做出更快的呼叫.编译器可以忽略它.

我对此提出异议,原因如下:

  1. 不会内联更大和递归的函数,编译器会忽略该inline关键字.
  2. 无论inline提及或不提及关键字,优化器都会自动内联较小的函数.

很明显,用户对使用关键字的函数内联没有任何控制权inline.

感知B

inline没有做内联的概念.把inline大/递归函数的未来将不利于小功能,不再需要它,为被内联.

确定使用的inline是保持一个定义规则.

即如果声明了一个函数,inline那么只有下面的东西是强制性的:

  1. 即使在多个转换单元中找到它的主体(例如,在多个.cpp文件中包含该头),编译器也只生成1个定义并避免多个符号链接器错误.(注意:如果该函数的主体不同,那么它是未定义的行为.)
  2. inline功能的主体必须在使用它的所有翻译单元中可见/可访问.换句话说,在任何一个文件中声明inline函数.h和定义将导致其他文件的"未定义的符号链接器错误" .cpp.cpp

判决书

"A"感觉完全错误,"B"感觉完全正确.

标准中有一些引用,但我期待一个答案,从逻辑上解释这个判决是真还是假.

jap*_*iss 56

我不确定你的说法:

较小的函数由优化器自动"内联",无论是否提及内联...很明显,用户对使用关键字的"内联"功能没有任何控制权inline.

我听说编译器可以自由地忽略你的inline请求,但我认为他们没有完全忽视它.

我查看了Github存储库中的Clang和LLVM以找出答案.(谢谢,开源软件!)我发现inline关键字确实使Clang/LLVM更有可能内联函数.

搜索

inlineClang存储库中搜索单词会导致令牌说明符kw_inline.看起来Clang使用一个聪明的基于宏的系统来构建词法分析器和其他与关键字相关的函数,因此有直接的想法if (tokenString == "inline") return kw_inline被发现.但是在ParseDecl.cpp中,我们看到kw_inline调用的结果DeclSpec::setFunctionSpecInline().

case tok::kw_inline:
  isInvalid = DS.setFunctionSpecInline(Loc, PrevSpec, DiagID);
  break;
Run Code Online (Sandbox Code Playgroud)

在该函数内部,如果它是重复的,我们设置一点并发出警告inline:

if (FS_inline_specified) {
  DiagID = diag::warn_duplicate_declspec;
  PrevSpec = "inline";
  return true;
}
FS_inline_specified = true;
FS_inlineLoc = Loc;
return false;
Run Code Online (Sandbox Code Playgroud)

FS_inline_specified其他地方搜索,我们看到它在位域中是一个位,并且它在getter函数中使用,isInlineSpecified():

bool isInlineSpecified() const {
  return FS_inline_specified | FS_forceinline_specified;
}
Run Code Online (Sandbox Code Playgroud)

搜索调用站点isInlineSpecified(),我们找到codegen,我们将C++解析树转换为LLVM中间表示:

if (!CGM.getCodeGenOpts().NoInline) {
  for (auto RI : FD->redecls())
    if (RI->isInlineSpecified()) {
      Fn->addFnAttr(llvm::Attribute::InlineHint);
      break;
    }
} else if (!FD->hasAttr<AlwaysInlineAttr>())
  Fn->addFnAttr(llvm::Attribute::NoInline);
Run Code Online (Sandbox Code Playgroud)

Clang到LLVM

我们完成了C++解析阶段.现在,我们的inline说明符将转换为与语言无关的LLVM Function对象的属性.我们从Clang切换到LLVM存储库.

寻找llvm::Attribute::InlineHint 产量的方法 Inliner::getInlineThreshold(CallSite CS) (带有可怕的无支撑if块):

// Listen to the inlinehint attribute when it would increase the threshold
// and the caller does not need to minimize its size.
Function *Callee = CS.getCalledFunction();
bool InlineHint = Callee && !Callee->isDeclaration() &&
  Callee->getAttributes().hasAttribute(AttributeSet::FunctionIndex,
                                       Attribute::InlineHint);
if (InlineHint && HintThreshold > thres
    && !Caller->getAttributes().hasAttribute(AttributeSet::FunctionIndex,
                                             Attribute::MinSize))
  thres = HintThreshold;
Run Code Online (Sandbox Code Playgroud)

因此,我们已经从优化级别和其他因素中获得了基线内联阈值,但如果它低于全局HintThreshold,我们会将其提升. (可以从命令行设置HintThreshold.)

getInlineThreshold()似乎只有一个呼叫站点,成员SimpleInliner:

InlineCost getInlineCost(CallSite CS) override {
  return ICA->getInlineCost(CS, getInlineThreshold(CS));
}
Run Code Online (Sandbox Code Playgroud)

getInlineCost在其成员指针上调用一个名为virtual的虚方法InlineCostAnalysis.

搜索::getInlineCost()查找作为类成员的版本,我们找到一个成员AlwaysInline- 这是一个非标准但广泛支持的编译器功能 - 而另一个是其成员InlineCostAnalysis.它在这里使用它的Threshold参数:

CallAnalyzer CA(Callee->getDataLayout(), *TTI, AT, *Callee, Threshold);
bool ShouldInline = CA.analyzeCall(CS);
Run Code Online (Sandbox Code Playgroud)

CallAnalyzer::analyzeCall()是超过200行,并确定功能是否可内联的真正的细节工作.它衡量了很多因素,但是当我们读完这个方法时,我们看到它的所有计算都要么操纵Threshold它们Cost.最后:

return Cost < Threshold;
Run Code Online (Sandbox Code Playgroud)

但命名的返回值ShouldInline实际上是用词不当.实际上,主要目的analyzeCall()是在对象上设置CostThreshold成员变量CallAnalyzer.返回值仅表示某些其他因素已覆盖成本与阈值分析的情况,如下所示:

// Check if there was a reason to force inlining or no inlining.
if (!ShouldInline && CA.getCost() < CA.getThreshold())
  return InlineCost::getNever();
if (ShouldInline && CA.getCost() >= CA.getThreshold())
  return InlineCost::getAlways();
Run Code Online (Sandbox Code Playgroud)

否则,我们返回一个存储Cost和的对象Threshold.

return llvm::InlineCost::get(CA.getCost(), CA.getThreshold());
Run Code Online (Sandbox Code Playgroud)

因此,在大多数情况下,我们不会返回"是或否"的决定.搜索继续!这个使用的返回值在哪里getInlineCost()

真正的决定

它被发现了 bool Inliner::shouldInline(CallSite CS).另一大功能.它getInlineCost()在开始时调用正确.

事实证明,getInlineCost分析内联函数的内在成本 - 它的参数签名,代码长度,递归,分支,链接等 - 以及关于函数使用的每个位置的一些聚合信息.另一方面,shouldInline()将此信息与有关使用该功能的特定位置的更多数据相结合.

在整个方法中都有调用InlineCost::costDelta()- 它将使用由InlineCosts Threshold计算的s 值analyzeCall().最后,我们返回一个bool.做出决定.在Inliner::runOnSCC():

if (!shouldInline(CS)) {
  emitOptimizationRemarkMissed(CallerCtx, DEBUG_TYPE, *Caller, DLoc,
                               Twine(Callee->getName() +
                                     " will not be inlined into " +
                                     Caller->getName()));
  continue;
}

// Attempt to inline the function.
if (!InlineCallIfPossible(CS, InlineInfo, InlinedArrayAllocas,
                          InlineHistoryID, InsertLifetime, DL)) {
  emitOptimizationRemarkMissed(CallerCtx, DEBUG_TYPE, *Caller, DLoc,
                               Twine(Callee->getName() +
                                     " will not be inlined into " +
                                     Caller->getName()));
  continue;
}
++NumInlined;
Run Code Online (Sandbox Code Playgroud)

InlineCallIfPossible()根据shouldInline()决定进行内联.

所以Thresholdinline关键字的影响,并最终用于决定是否内联.

因此,您的Perception B部分错误,因为至少有一个主要编译器根据inline关键字更改其优化行为.

但是,我们也可以看到这inline只是一个暗示,其他因素可能会超过它.

  • 男人,做腿法的方法.这是一个很好的答案. (3认同)
  • 来自Bjarne Stroustrup的评论:*"几十年来,人们已经承诺编译器/优化器现在或将很快比人类更好地进行内联.这在理论上可能是正确的,但对于优秀的程序员来说,它仍然没有实践,特别是在整个程序优化不可行的环境.明智地使用显式内联可以获得重大收益."* (2认同)

Mik*_*our 20

两者都是正确的.

使用inlinemay或者可能不会影响编译器内联任何特定函数调用的决定.所以A是正确的 - 它充当一个非绑定请求,调用函数内联,编译器可以自由忽略.

语义效应inline是放宽一个定义规则的限制,允许在多个翻译单元中使用相同的定义,如B中所述.对于许多编译器,这是允许内联函数调用所必需的 - 定义必须在点,编译器只需要一次处理一个翻译单元.

  • @Deduplicator:的确,这就是为什么我用"for many compilers"来限定它的原因.我想添加其他方案的简要描述,但这超出了简单问题的范围. (3认同)