见标题:C++ 函数内联意味着什么?
9 回答
它只意味着一件事和一件事:编译器将省略函数的多个定义。
一个函数通常不能定义多次(例如,如果您将非内联函数定义放入头文件中,然后将其#include 到多个编译单元中,您将收到链接器错误)。将函数定义标记为“内联”会抑制此错误(链接器确保发生正确的事情)。
它并不意味着更多!
最重要的是,这并不意味着编译器会将编译后的函数嵌入到每个调用站点中。是否发生这种情况完全取决于编译器的突发奇想,通常 inline 修饰符几乎不会改变编译器的想法。编译器可以——并且确实——内联未标记为内联的函数,并且它可以对标记为内联的函数进行函数调用。
删除多个定义是要记住的。
该函数被放置在代码中,而不是被调用,类似于使用宏(概念上)。
这可以提高速度(没有函数调用),但会导致代码膨胀(如果函数使用 100 次,您现在有 100 个副本)。
您应该注意,这不会强制编译器使函数内联,如果它认为这是一个坏主意,它将忽略您。同样,编译器可能会决定为您内联普通函数。
这也允许您将整个函数放在头文件中,而不是在 cpp 文件中实现它(无论如何您都不能这样做,因为如果它被声明为内联,那么您会得到一个未解析的外部,除非当然只有那个 cpp 文件用过)。
除了关于 的性能影响的其他(完全正确的)答案之外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
@OldMan
编译器仅在您要求时才内联非标记为内联函数。
仅当“请求”是指“打开优化”时。
它仅对事件的影响是正确的。
两者都是正确的。
内联不会生成链接器可能使用的任何额外信息。编译 2 目标文件并检查。它允许多个定义,因为符号没有导出!不是因为那是它的目标!
您是什么意思,“符号未导出”?内联函数不是静态的。他们的名字是可见的;他们有外部联系。引用 C++ 标准:
无效 h(); 内联无效 h(); // 外部链接
内联无效 l(); 无效 l(); // 外部链接
多重定义是非常重要的目标。这是强制性的:
内联函数应在使用它的每个翻译单元中定义,并且在每种情况下都应具有完全相同的定义(3.2)。[注意:在其定义出现在翻译单元之前,可能会遇到对内联函数的调用。] 如果具有外部链接的函数在一个翻译单元中声明为内联,则应在其出现的所有翻译单元中声明为内联;不需要诊断。具有外部链接的内联函数在所有翻译单元中应具有相同的地址。
函数体实际上插入到调用者函数中。因此,如果您多次调用此函数,您将获得代码的多个副本。好处是您可以更快地执行。
通常非常短的函数是内联的,当函数体的副本不会比为正常函数调用生成的通常的序言/结尾代码大很多时。
您可以在 MSDN 文章中阅读有关内联的更多信息 - http://msdn.microsoft.com/en-us/library/z8y1yy88.aspx
内联函数通过可能生成放置在应用程序代码段中的指令来改变应用程序的性能配置文件。函数是否内联由编译器决定。根据我的经验,大多数现代编译器都擅长确定何时遵守用户的内联请求。
在许多情况下,内联函数会提高其性能。函数调用存在固有的开销。然而,为什么内联函数可能是负数是有原因的:
- 通过复制代码来增加二进制可执行文件的大小可能会导致磁盘抖动,从而降低应用程序的速度。
- 内联代码可能会导致缓存未命中,或者可能会导致缓存命中,具体取决于您的架构。
C++ FAQ 很好地解释了关键字的复杂性:http: //www.parashift.com/c++-faq-lite/inline-functions.html#faq-9.3
通俗地说,就是允许编译器把函数的内容嫁接到调用点上,这样就没有函数调用了。如果你的函数有很大的控制语句(例如 ,if
等switch
),并且可以在编译时在调用站点评估条件(例如,在调用站点使用的常量值),那么你的代码最终会小得多(未使用的树枝被丢弃)。
更正式地说,内联函数也有不同的链接。我会让 C++ 专家谈谈这方面的问题。
与仅具有线性指令流相比,调用函数会给 CPU 带来一定的性能损失。CPU 的寄存器必须写入另一个位置,等等。显然,拥有函数的好处通常超过性能损失。但是,如果性能会成为问题,例如传说中的“内循环”函数或其他瓶颈,编译器可以将该函数的机器代码插入到主要执行流中,而不是通过 CPU 调用函数的税.
编译器允许将标记为内联的函数内联。无法保证编译器会这样做。编译器本身使用复杂的语义来决定何时执行或不执行。
当编译器决定一个函数应该被内联时,调用者代码中对该函数的调用被被调用者的代码替换。这意味着您可以节省堆栈操作、调用本身并提高代码缓存的局部性。有时这可能会带来巨大的性能提升。特别是在 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 ,编译器也不会导致错误 除非您要求编译器内联所有函数,否则不会发生内联。这不是大多数编译器的标准行为。