【问题标题】:What Does It Mean For a C++ Function To Be Inline?C++ 函数内联意味着什么?
【发布时间】:2010-09-14 11:04:02
【问题描述】:

见标题:C++ 函数内联意味着什么?

【问题讨论】:

  • 也许有 C++ 经验的人可以编辑问题以包含示例代码(即inline diff operator~ (...)

标签: c++ inline-functions


【解决方案1】:

这意味着一件事而且只有一件事:编译器将省略函数的多个定义。

一个函数通常不能被定义多次(即,如果你将一个非内联函数定义放到一个头文件中,然后#include 它到多个编译单元中,你会收到一个链接器错误)。将函数定义标记为“内联”会抑制此错误(链接器确保发生正确的事情)。

这并不意味着更多!

最重要的是,这并不意味着编译器会将编译后的函数嵌入到每个调用站点中。是否发生这种情况完全取决于编译器的突发奇想,通常 inline 修饰符几乎不会改变编译器的想法。编译器可以——并且确实——内联没有标记为内联的函数,并且它可以对标记为内联的函数进行函数调用。

要记住省略多个定义。

【讨论】:

  • 编译器仅在您要求时才内联非标记为内联函数。内联关键字确实会影响这一点。我知道,我在 GCC 代码方面做了很多工作,这些代码在大学时会产生编译器的专业化。
  • 我也在 GCC 上工作过。在这张海报中基本上是正确的。这篇文章应该在列表的顶部。
  • 它只对案例的影响是正确的。内联不会生成链接器可能使用的任何额外信息。编译 2 目标文件并检查。它允许多个定义,因为符号没有导出!不是因为那是它的目标!
【解决方案2】:

函数放在代码中,而不是被调用,类似于使用宏(概念上)。

这可以提高速度(没有函数调用),但会导致代码膨胀(如果函数被使用 100 次,你现在有 100 个副本)。

您应该注意,这不会强制编译器使函数内联,如果它认为这是一个坏主意,它将忽略您。同样,编译器可能会决定为您内联普通函数。

这也允许你将整个函数放在头文件中,而不是在 cpp 文件中实现它(无论如何你不能这样做,因为如果它被声明为内联,你会得到一个未解析的外部,除非当然只有该 cpp 文件使用了它)。

【讨论】:

  • 是的,关键是现代编译器自动内联,使用'inline'关键字只是对编译器的建议。现代编译器通常知道他们在做什么,并正确地选择内联或不内联,而不管“内联”关键字如何。但它仍然是一个有用的概念。
  • 注意:内联 not 意味着函数将被内联。它可能被内联,这仍然取决于编译器。但是,在语言/语义级别,内联意味着可以在多个模块中定义一个函数而不会出现链接时错误。 (但定义必须仍然匹配)。
  • "您应该注意这不会强制编译器将函数内联"
  • @FireLancer 我的理解是,经常调用函数时内联函数是件好事(stackoverflow.com/questions/1932311/…)。但是,您提到如果一个函数被使用 100 次,您将有 100 个副本,这会导致代码膨胀。你能澄清一下吗?
  • 这个答案的第一部分是 not 内联函数的含义。这就是内联函数调用的含义。只有最后一段是内联函数的含义。 “被内联”是函数的一个属性,但“被内联”不是,因为对同一个函数有多个调用,其中一些可能会被内联,而另一些则不会。
【解决方案3】:

除了关于inline 的性能影响的其他(完全正确的)答案之外,在 C++ 中,您还应该注意这可以让您安全地将函数放入标头中:

// my_thing.h
inline int do_my_thing(int a, int b) { return a + b; }

// use_my_thing.cpp
#include "my_thing.h"
...
    set_do_thing(&do_my_thing);

// use_my_thing_again.cpp
...
    set_other_do_thing(&do_my_thing);

这是因为编译器只在第一个需要编译常规可调用函数的目标文件中包含函数的实际主体(通常是因为它的地址已被占用,如上所示)。

如果没有 inline 关键字,大多数编译器会给出关于多重定义的错误,例如 MSVC:

use_my_thing_again.obj : error LNK2005: "int __cdecl do_my_thing(int,int)" (?do_my_thing@@YAHHH@Z) already defined in use_my_thing.obj
<...>\Scratch.exe : fatal error LNK1169: one or more multiply defined symbols found

【讨论】:

  • 正确答案在这里,不幸的是在此评论时只有第三个。
【解决方案4】:

@OldMan

编译器仅在您要求时才内联非标记为内联函数。

只有当“请求”是指“开启优化”时。

它只对事件的影响是正确的。

两者都是正确的。

内联不会生成链接器可能使用的任何额外信息。编译 2 目标文件并检查。它允许多个定义,因为符号没有导出!不是因为那是它的目标!

“符号未导出”是什么意思?内联函数不是静态的。他们的名字是可见的;他们有外部联系。引用 C++ 标准:

void h(); 内联无效 h(); // 外部链接

内联 void l(); 无效 l(); // 外部链接

多重定义是非常重要的目标。这是强制性的:

内联函数应在使用它的每个翻译单元中定义,并且应具有准确的 每种情况下的定义相同(3.2)。 [注意:对内联函数的调用可能会在其定义之前遇到 出现在翻译单元中。 ] 如果具有外部链接的函数在一个翻译中被声明为内联 单元,应在其出现的所有翻译单元中声明为内联;不需要诊断。一个 具有外部链接的内联函数在所有翻译单元中应具有相同的地址。

【讨论】:

    【解决方案5】:

    函数体实际上插入到调用者函数中。因此,如果您多次调用此函数,您将获得代码的多个副本。好处是您可以更快地执行。

    当函数体的副本不会比为正常函数调用生成的通常序言/结尾代码大不了多少时,通常会内联非常短的函数。

    您可以在 MSDN 文章中阅读更多关于内联的文章 - http://msdn.microsoft.com/en-us/library/z8y1yy88.aspx

    【讨论】:

      【解决方案6】:

      内联函数可能会生成放置在应用程序代码段中的指令,从而改变应用程序的性能配置文件。函数是否内联由编译器决定。根据我的经验,大多数现代编译器都擅长确定何时满足用户的内联请求。

      在许多情况下,内联函数会提高其性能。函数调用存在固有的开销。然而,为什么内联函数可能是负数是有原因的:

      • 通过复制代码来增加二进制可执行文件的大小可能会导致磁盘抖动,从而降低应用程序的速度。
      • 内联代码可能会导致缓存未命中,或者可能会导致缓存命中,具体取决于您的架构。

      C++ FAQ 很好地解释了关键字的复杂性: http://www.parashift.com/c++-faq-lite/inline-functions.html#faq-9.3

      【讨论】:

        【解决方案7】:

        通俗地说,就是允许编译器把函数的内容嫁接到调用点,这样就没有函数调用了。如果你的函数有很大的控制语句(例如,ifswitch 等),并且可以在编译时在调用站点评估条件(例如,在调用站点使用的常量值),那么你的代码结束小得多(未使用的分支被丢弃)。

        更正式地说,内联函数也有不同的链接。我会让 C++ 专家谈谈这方面的问题。

        【讨论】:

          【解决方案8】:

          调用函数会比仅具有线性指令流对 CPU 造成一定的性能损失。 CPU 的寄存器必须被写入另一个位置,等等。显然,拥有函数的好处通常超过性能损失。但是,如果性能会成为问题,例如传说中的“内循环”函数或其他瓶颈,编译器可以将该函数的机器代码插入到执行的主要流中,而不是通过 CPU 调用函数的税.

          【讨论】:

          • 你是绝对正确的,折叠内部循环并摆脱远跳转会减少由于缓存未命中而可能丢失的 CPU 周期,从而提高一侧的性能,但它会增加另一侧的缓存占用空间.这就是平衡这些取舍很重要的原因。
          【解决方案9】:

          编译器允许将标记为内联的函数内联。无法保证编译器会这样做。编译器本身使用复杂的语义来决定何时执行或不执行。

          当编译器决定一个函数应该被内联时,调用者代码中对该函数的调用被被调用者的代码替换。这意味着您可以节省堆栈操作、调用本身并提高代码缓存的局部性。有时这可能会带来巨大的性能提升。特别是在 1 行数据访问函数中,如面向对象代码中使用的访问器。

          成本通常会导致代码变大,这可能会损害性能。这就是为什么将函数设置为内联只是编译器的“绿旗”,它不需要遵循。编译器会尽力做到最好。

          对于不想处理链接特性的初学者来说,这是一个经验法则。内联函数将被同一编译单元中的其他函数调用。如果要实现一个可在多个编译单元上使用的内联函数,请将其作为一个头文件声明并实现内联函数。

          为什么?

          示例:在头文件 inlinetest.h

          int foo();
          inline int bar();
          

          在编译单元inlinetest.cpp

           int foo(){ int r = bar(); return r; }
          
          
           inline int bar(){ return 5;};
          

          然后在main.cpp

           #include "inlinetest.h"
           int main()
           {
            foo();
           //bar();
            }
          

          一次编译一个目标文件。如果您取消注释该“bar”调用,您将遇到错误。因为内联函数只在 inlinetest.o 目标文件上实现,并没有导出。同时 foo 函数,很可能已经嵌入了 bar 函数的代码(因为 bar 是单行无 I/O 操作,所以很可能是内联的)

          但是,如果您在头文件中声明了内联函数并内联实现了它,那么您将能够在包含该头文件的任何编译单元中使用它。 ("代码示例");

          删除 inline 关键字,编译器不会导致错误,即使在 main 调用 bar 并且除非您要求编译器内联所有函数,否则不会发生内联。这不是大多数编译器的标准行为。

          【讨论】:

            猜你喜欢
            • 2015-12-11
            • 1970-01-01
            • 2023-03-10
            • 2015-12-24
            • 2012-05-29
            • 1970-01-01
            • 2023-03-30
            • 2018-01-19
            相关资源
            最近更新 更多