如果我在头文件中定义一个非成员函数,它是否总是由编译器内联,还是编译器根据其启发式方法进行选择?我知道 __inline 只是一个提示,标题中的函数是否相同?
5 回答
请记住,包含标题中的某些内容与直接在源文件中键入内容没有什么不同。因此,就编译器而言,位于标头中没有任何区别。它从来不知道它在那里。
因此,当您在头文件中定义函数并将该头文件包含在文件中时,就像您只是将函数直接键入文件中一样。所以现在的问题是,“编译器是否选择基于启发式来内联事物?”
答案是“这取决于编译器”。该标准不保证内联或不内联的内容。也就是说,任何现代编译器都会非常聪明地了解它的内联内容,可能是启发式的。
然而,我们来到了一个有趣的地方。想象一下,您在头文件中有一个函数,并且将该头文件包含在多个源文件中。然后,您将拥有跨翻译单元的函数的多个定义,这违反了单一定义规则。因此,你会得到编译错误。(链接器错误通常类似于:“错误,函数 x 已在 y 中定义”)您可以做的是使用inline
关键字并且不再违反 ODR。
顺便说一句__inline
是非标准的。与您的帖子相反,它通常是强制内联的编译器扩展,而不是提示它。inline
是标准关键字,最初是为了暗示内联。就像您说的那样,大多数现代编译器在这方面完全忽略了它,如今它的唯一目的是提供内部链接。
来自C++ FAQ Lite:
无论您如何将函数指定为内联,它都是允许编译器忽略的请求:它可能内联扩展对内联函数的一些、全部或不调用。
它将根据启发式进行选择。确保将其明确声明为内联,否则如果将标头包含在多个编译单元中,则可能会出现重复的符号链接错误。
如果您在头文件中定义具有外部链接的函数并将其包含到多个翻译单元中,则会因违反单一定义规则 (ODR) 而导致编译错误(更准确地说:链接器错误)。所以答案是“否”:编译器不会将在头文件中定义函数作为内联的提示,也不会成为您不遵守 ODR 要求的借口。不仅这些函数不能保证是内联的,而且很可能你的程序甚至不会编译。
为了在头文件中定义一个函数并摆脱它,你必须给它内部链接(声明它static
,并在每个翻译单元中得到单独的函数),或者显式声明它inline
。
至于启发式...现代编译器通常会考虑几乎任何内联函数(通过应用启发式),无论它在哪里定义以及是否显式声明inline
。
标头中的函数没有魔法。编译器甚至不知道函数是否定义在头文件中。(由于标头实际上只是复制/粘贴到源文件中,您可以在标头中定义它,但编译器只是将其视为翻译单元的一部分)
“内联”还有两种不同的含义需要注意:
函数可以按照C++ 标准的定义进行内联:这可以通过在函数前面加上inline
关键字来完成,或者如果它是成员函数,则通过在类定义中就地定义它来完成。
这样做的效果是
- 通知链接器它可能会在多个文件中遇到函数定义,它应该只是默默地将它们合并在一起而不是抛出错误
- 使编译器更容易执行内联优化。
另一方面,内联优化只是用被调用函数的主体替换函数调用的行为,这意味着这种优化实际上应用于调用站点,而不是函数。函数可能在某些地方正常调用,但在其他地方内联。当编译器喜欢时,函数调用是内联的,最好在概念上将它与“内联”的第一个含义完全分开。
如果感觉像,编译器将在何时何地应用内联优化。它为此使用了很多启发式方法。较小的函数更有可能被内联。如果它确定一个特定的调用站点将被足够频繁地执行,则它更有可能被内联。最终,它使用的启发式方法是基于“它会提高还是降低性能”。它通常比人类更好地判断这一点,所以你不需要知道它使用了什么精确的启发式方法。内联过多只会损害性能。