【问题标题】:Does the compiler decide when to inline my functions (in C++)?编译器是否决定何时内联我的函数(在 C++ 中)?
【发布时间】:2010-11-15 08:29:40
【问题描述】:

我知道您可以使用 inline 关键字,或者只是将方法放在类声明中,比如短 ctor 或 getter 方法,但是编译器是否会最终决定何时内联我的方法?

例如:

inline void Foo::vLongBar()
{
   //several function calls and lines of code
}

如果编译器认为它会使我的代码效率低下,它会忽略我的内联声明吗?

作为一个附带问题,如果我在我的类之外声明了一个 getter 方法,如下所示:

void Foo::bar() { std::cout << "baz"; }

编译器会在幕后内联吗?

【问题讨论】:

    标签: c++ compiler-theory compiler-optimization


    【解决方案1】:

    是的,是否内联代码的最终决定权在于 C++ 编译器。 inline 关键字是一个建议,而不是要求。

    以下是有关如何在 Microsoft C++ 编译器中处理此决定的一些详细信息

    【讨论】:

    • 您能详细说明这是为什么吗?我认为 C++ 的重点是给程序员足够的绳索让自己上吊,但这似乎是限制性的。有充分的理由吗?
    • @Hooked,看看我发布的链接。它详细说明了为什么这并不总是一件好事。我想到的第一个项目是头递归函数。
    • 标准规定即使 __forceinline 也没有结束争论,编译器仍然有最终决定权。我可以理解什么时候内联是不合适的,但是如果程序员/分析器决定超越编译器,他有什么选择?该链接没有详细说明让编译器做出决定的理由。
    • 内联给定函数可能根本不可能。也就是说,应该注意的是,一些编译器至少仍然为您提供了覆盖最终决定的方法 - 例如__forceinline 在 VC++ 中。这仍然不允许您内联不可能内联的函数,但否则它将覆盖编译器的成本/收益分析。
    【解决方案2】:

    最终,函数是否内联完全取决于 编译器。通常,函数在流程方面越复杂,编译器内联它的可能性就越小。并且某些函数(例如递归函数)根本无法内联。

    不内联函数的主要原因是它会大大增加代码的整体大小,从而阻止 iot 被保存在处理器的缓存中。这实际上是一种悲观,而不是一种优化。

    至于让程序员决定在脚上或其他地方开枪,您可以自己内联函数 - 在函数的调用站点编写本应进入函数的代码。

    【讨论】:

    • 请查看我对 JaredPar 答案的评论。
    • 递归函数可以内联到指定的深度:)
    • 编译器如何知道编译时的深度?
    • @Neil Butterworth:我认为如果迭代次数是常数或 constexpr,编译器可以看到有多少次迭代。
    • 从 msdn 页面看来,内联递归必须扩展比实际需要的次数更多。它看起来可能是一种悲观(就代码大小而言)比优化(就性能而言)更多次。哦,顺便说一句,你能不能在你在这里发表的每一条评论后停止笑脸?您甚至在回答后得到了一个!
    【解决方案3】:

    正如许多人已经发布的那样,最终决定始终取决于编译器,即使您可以给出诸如 forceinline 之类的坚定提示。
    部分理由是内联不是自动的“更快”开关。过多的内联会使您的代码变得更大,并且可能会干扰其他优化。见The C++ FAQ Lite about inline functions and performance

    【讨论】:

    • 感谢您的链接。我已经阅读了大部分 Faq lite,但不是这个。
    【解决方案4】:

    正如其他人所指出的,inline 关键字只是对编译器内联代码的建议。由于编译器通常会内联未标记为inline 的代码,而不是具有标记的内联代码,因此该关键字似乎与register 或(C++0x 之前的)auto 一样多余。

    然而,inline 关键字还影响了另一件事:它将函数的 链接external(函数的默认值)更改为 内联。内联链接允许每个编译单元包含它自己的目标代码副本,并让链接器从最终的可执行文件中删除冗余副本。如果这让您想起模板,是的,模板也使用内联链接。

    【讨论】:

      【解决方案5】:

      是的,编译器拥有最终决定权。 In VS you can even inline recursive functions into specified depth ;)

      #pragma inline_depth( [0... 255] )
      

      【讨论】:

        【解决方案6】:

        只是为了加我的 5 美分 ...

        我发现这篇关于内联的Guru of Week 文章非常有用。

        据我所知,我在某处读到,当链接器链接目标文件并发现被链接的代码可以被内联时,即使是链接器也可能进行内联。

        【讨论】:

          【解决方案7】:

          作为一个附带问题,如果我在我的类之外声明了一个 getter 方法,如下所示:

          void Foo::bar() { std::cout << "baz"; }
          

          编译器会在幕后内联吗?

          这取决于。它可以用于同一翻译单元中的所有调用者(.cpp 文件及其所有 #included 定义)。但它仍然必须编译非内联版本,因为在翻译单元之外可能有该函数的调用者。您可能会在高优化级别上看到这一点(如果您的编译器确实可以做到的话)。 (特别是:比较将所有 .cpp 文件#include 到一个 .cpp 与典型布局时发生的情况。在一个翻译单元中使用所有定义,这种内联的机会显着增加。)

          【讨论】:

          • 哦,我不知道。我不知道链接器到底做了什么(我已经被我喜欢的 IDE 宠坏了,我从来不需要通过命令行编译并完成这个过程)。
          • 理解链接器是如何工作的应该是相当透明的(你不需要知道它来完成工作)。但最终在 C++ 中学习它变得特别重要,因为如果您不知道什么对链接器可见,什么不可见,那么许多语言结构的行为方式并不完全符合您的预期。 inline 是最大的一个,但是 .h.cpp 中的区别,static 的含义,模板可以放在哪里......这些都出现了。不幸的是,如果您对 C++ 没有基本的了解,那么链接真的会让您陷入困境。
          【解决方案8】:

          据我所知,如果编译器发现 for、while 等循环,它会自动将您声明为内联(或在类声明中编写)的函数设为非内联。 这是编译器在内联函数中拥有最后发言权的一个示例。

          【讨论】:

          • 我...你有任何来源支持这个说法吗?
          • 一个循环?为什么是循环?你不是说递归吗?
          • 它是特定于实现的。该标准没有说明何时可能不内联函数。只有那个实现有最终决定权。
          【解决方案9】:

          如果您确实、肯定、绝对地需要内联代码,那么总会有宏。 C 多年来一直支持这些,因为它们只是在编译之前的文本替换,它们真的,真的,内联你写的任何东西。

          这就是为什么 'inline' 关键字(甚至在某些情况下是强制变体)可以承受没有强制它的标准方式的原因 - 你总是可以只写一个宏。

          也就是说,inline 关键字通常更好,因为编译器通常知道将函数内联是否有意义,并且内联可以与编译器的其余优化交互。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2011-10-29
            • 2013-05-19
            • 1970-01-01
            • 2011-06-28
            相关资源
            最近更新 更多