编译器是否更可能使用指定的inline关键字在类声明中内联函数?

时间:2015-11-11 06:38:50

标签: c++ inline compiler-warnings compiler-optimization

我最近正在审查一位同事的代码,并注意到他已将#34; inline"在一个类声明中定义的一堆Getter函数前面的关键字。

例如

class Foo
{
public:
    inline bool GetBar() const { return m_Bar; }
private:
    bool m_Bar;
};

我在代码审查中建议他删除内联关键字,因为我在许多不同的地方读过,在类声明中定义函数是由编译器解释的(在这种情况下是MSVC,但显然作为C ++标准的一部分),表明作者希望内联函数。我的感觉是,如果额外的文字不能用于任何目的,它只是不必要的混乱,应该删除。

他的回答如下:

  1. inline关键字让其他与此代码交互的程序员清楚地知道这些函数是/应该内联的。

  2. 在这种情况下,许多编译器仍会考虑使用内联关键字,并使用它来影响(读取:增加)某种加权,用于确定所述函数是否实际上是内联的。

  3. 拥有内联关键字意味着"警告,如果没有内联"如果所述函数由于某种原因未被内联,则将触发警告(如果已启用)。

  4. 就个人而言,我完全不同意第一个原因。对我来说,拥有类声明中定义的函数足以表明意图。

    我对最后两个原因持怀疑态度。我无法找到任何确认或否认有关影响某种加权的内联关键字的观点的信息。我也很难触发"警告如果没有内联"对类声明中定义的函数的警告。

    如果您已经阅读过这篇文章,我想知道您是否对上述任何一点有任何见解?另外,如果你能指出我的任何相关文章/文件,我真的很感激。

    谢谢!

3 个答案:

答案 0 :(得分:5)

编辑1:添加了LLVM(换言之,“clang”)内联代码

编辑2:添加有关如何“解决”此问题的说明。

实际正确

第1点当然是不言自明的。

第2点是无意义的 - 所有现代编译器(至少MS,GCC和Clang [aka XCode])完全忽略inline个关键字,并且纯粹基于频率/大小critera决定(确定基于“代码膨胀因子”)在大小*次数上,所以只调用几次的小函数或函数更有可能被内联 - 当然,getter将是编译器内联的完美选择,因为它只有两三个指令,并且最有可能比加载this更短,然后调用getter函数。

inline关键字根本没有任何区别[并且C ++标准规定类中的定义无论如何都是inline

第3点是另一种看似合理的情况,但我认为它的定义隐含内联的事实应该给出相同的结果。前一段时间讨论了inline关键字及其在Clang邮件列表中的含义,结论是“编译器通常最了解”。

inline与虚函数一起使用通常也毫无用处,因为它们几乎总是通过vtable条目调用,并且不能内联。

编辑1:

从LLVM的“InlineCost.cpp”获取的代码:

InlineCost InlineCostAnalysis::getInlineCost(CallSite CS, Function *Callee,
                                             int Threshold) {
  // Cannot inline indirect calls.
  if (!Callee)
    return llvm::InlineCost::getNever();

  // Calls to functions with always-inline attributes should be inlined
  // whenever possible.
  if (CS.hasFnAttr(Attribute::AlwaysInline)) {
    if (isInlineViable(*Callee))
      return llvm::InlineCost::getAlways();
    return llvm::InlineCost::getNever();
  }

  // Never inline functions with conflicting attributes (unless callee has
  // always-inline attribute).
  if (!functionsHaveCompatibleAttributes(CS.getCaller(), Callee,
                                         TTIWP->getTTI(*Callee)))
    return llvm::InlineCost::getNever();

  // Don't inline this call if the caller has the optnone attribute.
  if (CS.getCaller()->hasFnAttribute(Attribute::OptimizeNone))
    return llvm::InlineCost::getNever();

  // Don't inline functions which can be redefined at link-time to mean
  // something else.  Don't inline functions marked noinline or call sites
  // marked noinline.
  if (Callee->mayBeOverridden() ||
      Callee->hasFnAttribute(Attribute::NoInline) || CS.isNoInline())
    return llvm::InlineCost::getNever();

  DEBUG(llvm::dbgs() << "      Analyzing call of " << Callee->getName()
        << "...\n");

  CallAnalyzer CA(TTIWP->getTTI(*Callee), ACT, *Callee, Threshold, CS);
  bool ShouldInline = CA.analyzeCall(CS);

  DEBUG(CA.dump());

  // 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();

  return llvm::InlineCost::get(CA.getCost(), CA.getThreshold());
}

可以看出(在其余代码中进行了一些挖掘),只检查“始终”和“从不”内联选项。内联关键字本身没有。

[请注意,这是clang和clang ++的内联代码 - clang本身在生成代码时没有做任何特别聪明的事情,它只是“正在”(令数百名程序员在该项目上花费了数百年的时间!) C和C ++的解析器转换为LLVM IR,所有好的,聪明的东西都在LLVM层完成 - 这实际上是提供“多语言”编译器框架的好方法。我编写了一个Pascal编译器,尽管编译工作是一个完整的新手,但我的编译器(使用LLVM生成实际的机器代码)在基准(生成的代码)方面比Free Pascal更好 - 所有这都要归功于LLVM,几乎没有这是我的工作 - 除了一些代码在一个特定的基准测试中内联一些常用的函数]

我没有MS编译器的访问源(doh!),我只是为了检查这个而无法下载gcc。我知道,根据经验,所有三个内联函数都没有内联关键字,而gcc将积极地内联函数,它可以确定只有一个调用者(例如,大static辅助函数)

编辑2:

解决此类问题的正确方法是制定一个编码标准,清楚地说明应该使用inline [和类内定义的函数]的时间和地点,以及何时不应该完成。如果当前,其他类中的其他小getter函数都没有inline,那么这个函数就会很奇怪并且很突出。如果除了一些人之外都有inline,那么可能也应该修复。

另一个传感器:我个人喜欢把if语句写成

if (some stuff goes here)

(if和括号之间的空格,但不在里面的东西) 但工作中的编码标准说:

if( some stuff goes here )

(if和括号之间没有空格,但是里面的东西周围没有空格)

我不小心得到了这样的东西:

if ( some stuff goes here )

在一些需要审核的代码中。我修复了这一行,但是决定在if之后用空格修复175个其他if语句 - 总共那个文件中的if语句少于350个,所以超过一半的语句不正确.. 。

答案 1 :(得分:3)

对于您提到的特定示例,backgroundColor被隐式内联,这使得UILabel关键字变得多余。第7.1.2节C ++标准中的P3说:

  

在类定义中定义的函数是内联函数。

VC ++ documentation也说明了相同的内容:

  

通过使用,可以内联声明类的成员函数   内联关键字或将函数定义放在类中   定义

一般情况下,您不需要使用GetBar关键字。 C / C ++专家曾在2002年曾说过:

  

inline关键字允许程序员告诉编译器   它可能很难自动搞清楚。   但是,将来,编译器可能会做得更好   制定内联决策而不是程序员。当发生这种情况时,   内联关键字可能被视为时间的古怪提示   程序员被迫担心代码生成的细节。

您可以找到完整的文章here。您应该阅读整篇文章,以了解为什么将关键字添加到C99。本文还讨论了inline函数。

现在我们处于未来,事实上,现代编译器非常复杂,不再需要这个关键字。唯一的例外是使用inline函数时。

  

inline关键字对编译器有任何影响吗?   决定内联函数?

在MSVC中,内联关键字可能会影响编译器的决策although the compiler may choose to ignore this hint if inlining would be a net loss

  

inline关键字使其他程序员能够清楚地进行交互   使用此代码,这些函数应该/应该内联。

这是一个无效的原因。程序员是一个人。对于人来说,关于是否内联函数,做出比MSVC更好的决定通常是非常困难的。第二,当你告诉他/她应该内联一个函数时,程序员应该做什么?编译器是正在进行内联的编译器。警告本身并不会告诉您是否必须对此采取任何措施,如果是这样的话,该怎么办。

  

在这种情况下,许多编译器仍会考虑使用内联关键字   并使用它来影响(读取:增加)某种加权   用于决定是否实际上内联所述功能。

在MSVC中也是如此。但现代的MSVC不再需要这个提示了。如果内联函数是有益的,编译器将知道并将内联它。如果不是,它将忽略人类插入的内联关键字。

  

使用内联关键字意味着&#34;警告,如果没有内联&#34;   如果所述功能不是,则将触发警告(如果已启用)   无论出于何种原因内联。

当为内联选择的函数为C4710时,MSVC编译器会发出警告not inlined。默认情况下禁用此警告,因为大多数开发人员都不关心(并且不应该)关注此警告。 You can enable this warning, however。除非您是编译器优化研究员,并且想要了解MSVC使用的内联算法,否则您不应该启用这些警告。

答案 2 :(得分:1)

我忽略了问题的C ++特定部分,而只关注#2项目符号的普通内联函数(C或C ++):

  

我对最后两个原因表示怀疑。我找不到任何可以证实或否认有关inline关键字影响某种加权的观点的信息。

https://blog.tartanllama.xyz/inline-hints/提供了一些细节,日期为2018年1月。简而言之:Clang + LLVM和GCC在决定是否内联函数时都考虑了关键字。这不是唯一的考虑因素,但确实会影响决策。