内联函数在C ++中的好处?


254

在C ++中使用内联函数的优点/缺点是什么?我看到它只会提高编译器输出的代码的性能,但是使用当今优化的编译器,快速的CPU,巨大的内存等(不像1980年那样,当时内存不足,所有内容都必须容纳100KB内存),他们今天真的有优势吗?


48
这是常识错误的问题之一。每个人都回答了标准的Comp Sci答案。(内联节省了函数调用成本,但增加了代码大小)。垃圾。它为编译器提供了一种应用更多优化的简单机制。
马丁·约克

37
这是伪装成评论的答案之一。如果您不喜欢所发布的任何答案,请发布您自己的答案并查看其结果。
Dave Van den Eynde

10
这个问题的基础是有缺陷的。C ++内联函数与编译期间内联的编译器无关。不幸的是,它inline是一个c ++关键字,而内联是一种编译器优化技术。有关正确答案,请参见此问题“ 何时应inline为功能/方法编写关键字 ”。
deft_code 2011年

3
@JoseVega您的链接被弄乱了-当前链接为exforsys.com/tutorials/c-plus-plus/inline-functions.html

Answers:


143

内联函数更快,因为您不需要像往常一样将参数和返回地址压入/弹出堆栈。但是,它的确会使您的二进制文件稍大。

它有很大的不同吗?对于大多数人来说,在现代硬件上还不够明显。但这可以有所作为,对某些人来说就足够了。

内联标记并不能保证它是内联的。这只是对编译器的建议。有时,例如当您具有虚函数或涉及递归时,这是不可能的。有时,编译器只是选择不使用它。

我可以看到这样的情况产生了明显的变化:

inline int aplusb_pow2(int a, int b) {
  return (a + b)*(a + b) ;
}

for(int a = 0; a < 900000; ++a)
    for(int b = 0; b < 900000; ++b)
        aplusb_pow2(a, b);

26
我怀疑内联对上面没有影响。与gcc 4.01一起编译。版本1强制使用内联:48.318u 1.042s 5:51.39 99.4%0 + 0k 0 + 0io 0pf + 0w版本2强制没有内联348.311u 1.019s 5:52.31 99.1%0 + 0k 0 + 0io 0pf + 0w这是一个很好的例子是常识是错误的。
马丁·约克

36
虽然通话本身确实很重要,但这只是使用内联所获得的次要收益。主要的好处是,编译器现在可以看到指针彼此之间不互为别名,调用者的变量最终位于被调用者中,依此类推。因此,以下优化更为重要。
Johannes Schaub-litb

31
由于该函数的结果从未使用过,因此该函数也没有副作用,因此在此代码段中可能没有什么不同。我们在图像处理的内联中看到了可观的性能提升。
基座

4
没有差异的原因可能是编译器可以自行内联。或代码很小,因此没有代码预取问题。
einpoklum 2015年

3
因此,编译器甚至可能优化了整个循环。
noɥʇʎԀʎzɐɹƆ

197

优点

  • 通过在需要的地方内联代码,您的程序将在函数调用和返回部件上花费更少的时间。它应该使您的代码运行得更快,即使它变得更大(请参见下文)。内联琐碎的访问器可能是有效内联的一个示例。
  • 通过将其标记为内联,可以将函数定义放在头文件中(即,可以将其包含在多个编译单元中,而无需链接程序抱怨)

缺点

  • 它可以使您的代码更大(即,如果对非平凡函数使用内联)。这样,它可能会引起分页并破坏编译器的优化。
  • 它会稍微破坏您的封装,因为它公开了对象处理的内部(但随后,每个“私有”成员也会)。这意味着您不能在PImpl模式中使用内联。
  • 它稍微破坏了您的封装2:C ++内联在编译时已解决。这意味着,如果您更改内联函数的代码,则需要使用它重新编译所有代码,以确保将其更新(出于相同的原因,我避免使用函数参数的默认值)
  • 当在标头中使用时,它会使标头文件变大,因此,它将用用户不关心的代码来稀释有趣的信息(例如类方法的列表)(这就是我在a内声明内联函数的原因)类,但将在类主体之后的标头中定义它,而不是在类主体中定义它)。

内联魔术

  • 编译器可以内联或不内联您标记为内联的函数;它也可能决定在编译或链接时内联函数未标记为内联。
  • 内联的工作方式类似于由编译器控制的复制/粘贴,这与预处理器宏有很大不同:宏将被强制内联,将污染所有名称空间和代码,将不容易调试,甚至可以完成如果编译器认为它效率低下。
  • 在类本身内定义的类的每个方法都被视为“内联”(即使编译器仍可以决定不对其进行内联)
  • 虚拟方法不应该是不可移植的。尽管如此,有时,当编译器可以肯定地知道对象的类型时(即,该对象是在同一函数体内声明并构造的),即使是虚函数也将被内联,因为编译器确切地知道了对象的类型。
  • 模板方法/函数并不总是内联的(它们在标题中的存在不会使其自动内联)。
  • “内联”之后的下一步是模板元编程。即,通过在编译时“内联”代码,有时,编译器可以推断出函数的最终结果...因此,有时可以将复杂的算法简化为一种return 42 ;语句。对我来说,这是极端的内联。它在现实生活中很少发生,它使编译时间更长,不会膨胀您的代码,并使您的代码更快。但是,就像圣杯一样,不要尝试将其应用到任何地方,因为大多数处理都无法通过这种方式解决...尽管如此,这仍然很酷...
    :-p

您说过,这会稍微破坏您的封装。您能用一个例子解释一下吗?
破坏者

6
@PravasiMeet:它是C ++。假设您将DLL /共享库交付给客户端,然后针对该客户端进行编译。使用成员变量X并执行Y的内联函数foo将内联到客户端代码中。假设需要交付DLL的更新版本,将成员变量更改为Z,并在工作Y之外添加工作YY。客户端仅将DLL复制到其项目和BOOM中,因为foo的代码二进制文件中的内容不是您编写的更新代码...尽管客户端无法合法访问您的私人代码,但内联使其变得“公开”。
paercebal

@paercebal关于倒数第二个要点,您能举一个当函数模板不是内联时的例子吗?我以为它们总是内联的,尽管我现在没有参考资料了(不过简单的测试似乎可以确认这一点)。
康拉德·鲁道夫

@KonradRudolph在n4594中,我看到了:3.2/6: There can be more than one definition of [..] inline function with external linkage [..] non-static function template。在5.1.5 / 6 For a generic lambda, the closure type has a public inline function call operator member template。并7.1.2/2: the use of inline keyword is to declare an inline function建议在调用点内联函数体。因此,我得出结论,即使内联函数和函数模板可以表现相同,它们仍然是相互独立的,可以混合的正交概念(即内联函数模板)
paercebal 2016年

封装坏了吗?为何如此?封装是针对小伙子编程的,而不是针对内存中的实际对象的。那时没有人在乎。即使您分发了一个库,编译器也可能自行选择内联还是不进行内联。因此,最后获得新的库时,只需重新编译使用该库中的函数和对象的所有内容。
FalcoGer

42

在古老的C和C ++中,inline就像register:对编译器的关于可能的优化的建议(仅是建议)。

在现代C ++中,inline告诉链接器,如果在不同的翻译单元中找到多个定义(不是声明),则它们都相同,并且链接器可以自由保留一个,而丢弃所有其他定义。

inline 如果在头文件中定义了一个函数(无论多么复杂或“线性”),则该函数是强制性的,以允许多个源包含该函数而不会导致链接程序出现“多个定义”错误。

默认情况下,在类内部定义的成员函数为“内联”,而模板函数也为“内联”(与全局函数相反)。

//fileA.h
inline void afunc()
{ std::cout << "this is afunc" << std::endl; }

//file1.cpp
#include "fileA.h"
void acall()
{ afunc(); }

//main.cpp
#include "fileA.h"
void acall();

int main()
{ 
   afunc(); 
   acall();
}

//output
this is afunc
this is afunc

请注意,将fileA.h包含在两个.cpp文件中,从而导致两个实例afunc()。链接器将丢弃其中之一。如果未inline指定,则链接器将抱怨。


16

内联是对编译器的建议,可以随意忽略。是少量代码的理想选择。

如果您的函数是内联的,则基本上是将其插入到对其进行函数调用的代码中,而不是实际调用单独的函数。由于您不必进行实际通话,因此可以提高速度。

它还可以帮助CPU进行流水线操作,因为它们不必使用调用引起的新指令来重新加载流水线。

唯一的缺点是可能增加二进制文件的大小,但是,只要函数很小,就没有太大关系。

如今,我倾向于将这类决策留给编译器(无论如何,还是明智的决策)。编写它们的人往往对底层体系结构有更详细的了解。


12

内联函数是编译器使用的优化技术。可以简单地在函数原型之前添加inline关键字以使函数内联。内联函数指示编译器在代码中使用该函数的位置插入该函数的完整主体。

优点 :-

  1. 它不需要函数调用开销。

  2. 它还可以在函数调用时节省堆栈上变量push / pop的开销。

  3. 它还节省了从函数返回调用的开销。

  4. 它通过利用指令高速缓存来增加引用的局部性。

  5. 内联之后,如果指定,编译器还可以应用过程内优化。这是最重要的一个,通过这种方式,编译器现在可以专注于消除死代码,可以更加注重分支预测,归纳变量消除等。

要查看更多信息,请点击以下链接 http://tajendrasengar.blogspot.com/2010/03/what-is-inline-function-in-cc.html


4
1)这是一个建议,而不是一条指令2)如果经常内联一个常用函数,则由于代码大小的增加,它可能导致更多的高速缓存未命中
Flexo

3
末尾的链接是您的个人博客吗?如果是这样,则应这样声明,否则看起来像垃圾邮件。
柔印

6

我想补充一点,在构建共享库时,内联函数至关重要。无需内联标记函数,它将以二进制形式导出到库中。如果已导出,它还将出现在符号表中。另一方面,内联函数既不导出到库二进制文件也不导出到符号表。

当打算在运行时加载库时,这可能至关重要。它还可能会访问二进制兼容感知库。在这种情况下,请勿使用内联。


@Johnsyweb:请仔细阅读我的答案。当您构建可执行文件时,您所说的是正确的。但是编译器不能简单地忽略inline构建一个共享库时!
doc 2010年

4

在优化过程中,即使您未标记函数,许多编译器也会内联函数。如果您不了解编译器,通常只需要将函数标记为内联即可,因为它通常可以自行做出正确的决定。


许多编译器也不会这样做,例如MSVC不会这样做,除非您告诉它
paulm 2014年

4

inline允许您#include在不违反一个定义规则的情况下将函数定义放在头文件中,并将该头文件放在多个源文件中。


3

一般来说,如今,任何现代编译器都担心内联任何东西,这几乎是在浪费时间。编译器实际上应该通过自己对代码的分析以及传递给编译器的优化标志的规范来为您优化所有这些注意事项。如果您关心速度,请告诉编译器优化速度。如果您关心空间,请告诉编译器优化空间。作为另一个暗示,一个体面的编译器甚至会在真正有意义的情况下自动内联。

另外,正如其他人所述,使用内联并不能保证任何内联。如果要保证它,则必须定义一个宏而不是一个内联函数。

何时内联和/或定义一个宏以强制包含?-仅当已知的关键代码段的速度得到了证明的和必要的事实证明的提高时,已知该代码的关键部分会影响应用程序的整体性能。


…如果关心空间,请告诉编译器优化空间 -告诉编译器优化速度可以使用C ++和C生成较小的二进制文件。类似地,告诉编译器优化空间可以提高执行速度。现在,这些功能并不总是像广告中那样起作用。与编译器的广义解释(必须保持快速可用)相比,人类有能力更好地理解其程序的某些方面。人为干预不一定是一件坏事。
贾斯汀

3

这并不关乎性能。C ++和C均用于硬件上的嵌入式编程。例如,如果要编写中断处理程序,则需要确保可以立即执行代码,而无需交换其他寄存器和/或内存页。那就是内联派上用场的时候。优秀的编译器在需要速度时会自行执行一些“内联”,但是会“强制内联”。


1

将函数内联到so库中会遇到同样的麻烦。内联函数似乎没有编译到库中。结果,如果可执行文件要使用库的内联函数,则链接器会发出“未定义引用”错误。(发生在我用gcc 4.5编译Qt源代码的时候。


1

为什么不默认使所有函数内联?因为这是工程上的折衷。至少有两种类型的“优化”:加速程序并减小程序的大小(内存占用)。内联通常会加快处理速度。它消除了函数调用开销,避免了从堆栈中压入然后拉出参数。但是,这也使程序的内存占用量更大,因为现在必须使用函数的完整代码替换每个函数调用。要使事情变得更加复杂,请记住,CPU将频繁使用的内存块存储在CPU的高速缓存中,以进行超快速访问。如果使程序的内存映像足够大,则程序将无法有效使用缓存,并且在最坏的情况下,内联实际上会降低程序的速度。


1

我们的计算机科学教授敦促我们不要在c ++程序中使用内联。当被问到为什么时,他友好地向我们解释了现代编译器应该检测何时自动使用内联。

所以是的,内联可以是在可能的情况下使用的一种优化技术,但是很显然,只要有可能内联函数,这已经为您完成。


5
不幸的是,你的教授是完全错误的。inlineC ++中的C有两个非常不同的含义-只有其中一个与优化有关,而您的教授在这方面是正确的。但是,inline满足“ 一个定义规则”通常是的第二含义。
康拉德·鲁道夫

-1

来自此处另一次讨论的结论:

内联函数有什么缺点吗?

显然,使用内联函数没有错。

但是值得注意以下几点!

  • 内联的过度使用实际上会使程序变慢。根据函数的大小,对其进行内联会导致代码大小增加或减少。内联一个非常小的访问器函数通常会减小代码大小,而内联一个非常大的函数则可以大大增加代码大小。在现代处理器上,由于更好地使用了指令高速缓存,因此较小的代码通常可以更快地运行。-Google指南

  • 内联函数的速度优势往往会随着函数大小的增加而减少。在某些时候,与函数主体的执行相比,函数调用的开销变小,并且失去了好处-源

  • 内联函数在某些情况下可能不起作用:

    • 对于返回值的函数;如果存在return语句。
    • 对于不返回任何值的函数;如果存在循环,开关或转到语句。
    • 如果函数是递归的。-资源
  • __inline仅当您指定optimize选项时,该关键字才会使函数内联。如果指定了optimize,则是否接受优化__inline取决于内联优化器选项的设置。默认情况下,只要运行优化器,内联选项就会生效。如果指定optimize,则如果要__inline忽略关键字,还必须指定noinline选项。-资源


3
如果内联是命令而不是对编译器的提示,则为true。编译器实际上决定要内联的内容。
马丁·约克

1
@LokiAstari我知道内联是对编译器的请求。我的观点是,如果它对编译器有提示,我们应该将其留给编译器来决定最佳选择。为什么以内联方式使用内联,即使您使用内联,仍然由编译器做出最终决定。我还想知道我的微软是否已引入_forceinline。
克里希纳(Krishna Oza)2014年

@krish_oza:我的评论在这里。关于这个答案。答案是完全错误的。因为编译器会忽略用于确定是否内联代码的inline关键字所以上面提到的所有观点都是错误的。如果编译器使用关键字进行内联(它们仅用于确定标记多个定义以进行链接),则它们将为true。
马丁·约克
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.