C++函数被声明为内联函数是什么意思?

32

请看标题:C++中的inline函数是什么意思?


也许有更有经验的C++程序员可以编辑问题,包括示例代码(例如inline diff operator~ (...))。 - puk
9个回答

46

这句话只有一个含义:编译器将省略函数的多个定义。

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

它不代表任何其他意思!

最重要的是,它并不意味着编译器将在每个调用点嵌入已编译的函数。是否会这样做完全取决于编译器的心情,通常内联修饰符对改变编译器的想法几乎没有或根本没有影响。编译器可以(而且确实会)内联未标记为内联的函数,并且可以对标记为内联的函数进行函数调用。

记住省略多个定义就行了。


编译器只有在你明确要求时才会内联非标记为内联的函数。内联关键字并不影响这一点。我知道,我在大学时曾经在GCC代码中工作过,以此来生成编译器的特化版本。 - user23415
9
我也曾经参与过GCC的工作。这张海报基本上是正确的。这篇文章应该排在列表的最顶端。 - Martin York
它只在效果上正确,而不是原因上。内联不会生成链接器可能使用的任何额外信息。编译两个目标文件并检查。它允许多个定义,正是因为这些符号没有被导出!而不是因为这是它的目标! - user23415

39

该函数直接被放在代码中,而不是通过调用,类似于使用宏定义(概念上相似)。

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

需要注意的是,这并不强制编译器将函数内联,并且如果编译器认为这是一个坏主意,它将忽略你。同样,编译器也可能决定为您将普通函数内联。

这还允许您将整个函数放在头文件中,而不是在cpp文件中实现它(反正您不能这样做,因为如果声明了内联,则会得到无法解析的外部引用,除非当然只有该cpp文件使用它)。


1
是的,而且它的关键点在于现代编译器会自动进行内联,并使用“inline”关键字只是向编译器提出建议。现代编译器通常知道自己在做什么,并正确选择是否内联,而不管“inline”关键字。但这仍然是一个有用的概念。 - Jamie
9
注意:inline并不意味着函数将被内联。它可能会被内联,但这仍然取决于编译器。不过,在语言/语义层面上,inline表示一个函数可以在多个模块中定义而不会产生链接时错误(尽管定义仍然必须匹配)。 - Aaron
4
请注意,这并不强制编译器将该函数作为内联函数处理。 - Fire Lancer
1
@FireLancer 我的理解是,当一个函数被频繁调用时(https://dev59.com/1nI-5IYBdhLWcg3wSGMB),将其内联化是有益的。然而,你提到如果一个函数被使用了100次,那么就会有100个副本,这会导致代码膨胀。你能否请澄清一下? - Mustafa
这个答案的第一部分 并不是 函数成为内联函数的含义。它描述的是函数调用被内联的含义。只有最后一段才是函数成为内联函数的含义。"成为内联函数" 是函数的一个属性,但 "被内联"却不是,因为在多次调用同一函数的情况下,可能有些调用会被内联,而有些则不会。 - Steve Jessop

23

除了关于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

很遗憾,目前只有第三个答案是正确的。 - deadalnix

3

@OldMan

编译器只有在你要求它这样做时才会内联非标记为内联的函数。

只有当“要求”指的是“开启优化”时才会内联。

它只正确地反映了结果而非原因。

在两者方面都是正确的。

内联不会生成链接器可以使用的任何额外信息。编译2个对象文件并检查。正是因为符号未被导出,所以允许多个定义!这不是其目标!

您说的“符号未被导出”是什么意思?内联函数不是静态的。它们的名称是可见的;它们具有外部链接性。引用C++标准:

void h(); inline void h(); // 外部链接 inline void l(); void l(); // 外部链接

多个定义的事情非常重要。这是强制性的:

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


2
该函数体字面上被插入到调用函数内部。因此,如果您对该函数进行多次调用,您将获得代码的多个副本。好处是您会得到更快的执行速度。
通常,非常短的函数会被内联,当函数体的复制不比通常的前导/后导代码生成的函数调用大多少时。
您可以在MSDN有关内联的文章中阅读更多信息- http://msdn.microsoft.com/en-us/library/z8y1yy88.aspx

1

内联函数通过可能生成放置在应用程序代码段中的指令来改变应用程序的性能特征。是否内联函数取决于编译器的自由裁量权。根据我的经验,大多数现代编译器都擅长确定何时遵守用户的内联请求。

在许多情况下,内联函数将提高其性能。函数调用本身存在固有开销。然而,内联函数可能会产生负面影响的原因有:

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

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


0

非正式地说,这意味着编译器允许将函数的内容嫁接到调用点上,从而消除函数调用。如果您的函数具有大型控制语句(例如ifswitch等),并且在调用点可以在编译时评估条件(例如在调用点使用常量值),那么您的代码最终会变得更小(未使用的分支会被删除)。

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


0

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


1
你说得完全正确,折叠内循环和消除远跳转会减少由于缓存未命中而可能丢失的CPU周期,因此从一方面提高了性能,但从另一方面增加了缓存占用。这就是为什么平衡这些权衡是很重要的。 - Josef

0

将函数标记为inline的作用是允许编译器对其进行内联。但并不保证编译器一定会这么做。编译器本身使用复杂的语义来决定是否内联。

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

代价是通常会导致更大的代码,这可能会损害性能。这就是为什么将函数设置为inline只是向编译器发出的“绿灯”,而不需要遵循的原因。编译器将尝试做出最佳决策。

对于不想处理链接特殊性的初学者来说,一个经验法则是:inline函数应该由同一编译单元中的其他函数调用。如果您想实现一个可以在多个编译单元中使用的内联函数,请将其制作成头文件声明和实现的内联函数。

为什么?

例如:在头文件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操作,因此很可能被内联)

但是,如果在头文件中声明了内联函数并将其实现为内联,则可以在包含该头文件的任何编译单元中使用它。 (“代码示例”);

删除内联关键字,编译器将不会引起错误,即使在主函数中调用bar,也不会发生内联,除非您要求编译器内联所有函数。这不是大多数编译器的标准行为。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接