15

有些人喜欢使用inline关键字 in C,并把大函数放在标题中。你什么时候认为这是无效的?我有时甚至认为它很烦人,因为它不寻常。

我的原则是inline应该用于非常频繁访问的小函数,或者为了进行真正的类型检查。无论如何,我的品味指导我,但我不知道如何最好地解释为什么inline对大型功能没有那么有用的原因。

这个问题中,人们建议编译器可以更好地猜测正确的事情。这也是我的假设。当我尝试使用这个参数时,人们回答它不适用于来自不同对象的函数。好吧,我不知道(例如,使用 GCC)。

感谢您的回答!

4

12 回答 12

26

inline做两件事:

  1. 使您免于“单一定义规则”(见下文)。这始终适用。
  2. 为编译器提供避免函数调用的提示。编译器可以随意忽略这一点。

#1 可能非常有用(例如,如果很短,则将定义放在标题中)即使 #2 被禁用。

在实践中,编译器通常会更好地确定要内联的内容(尤其是在配置文件引导优化可用的情况下)。


[编辑:完整参考文献和相关文本]

以上两点均遵循 ISO/ANSI 标准(ISO/IEC 9899:1999(E),俗称“C99”)。

在第 6.9 节“外部定义”第 5 段中:

外部定义是一个外部声明,它也是函数(内联定义除外)或对象的定义。如果用外部链接声明的标识符在表达式中使用(而不是作为结果为整数常量的 sizeof 运算符的操作数的一部分),则在整个程序的某处,该标识符应该只有一个外部定义;否则,不得超过一个。

虽然 C++ 中的等价定义明确命名为单一定义规则 (ODR),但它的用途相同。外部(即不是“静态的”,因此对于单个翻译单元来说是本地的——通常是单个源文件)只能定义一次,除非它是一个函数内联。

在第 6.7.4 节“函数说明符”中,定义了 inline 关键字:

使函数成为内联函数意味着对函数的调用尽可能快。[118]此类建议的有效程度由实施定义。

和脚注(非规范),但提供说明:

例如,通过使用通常的函数调用机制的替代方法,例如“内联替换”。内联替换不是文本替换,也不会创建新函数。因此,例如,在函数体中使用的宏的扩展使用它在函数体出现时的定义,而不是调用函数的位置;标识符指的是正文出现的范围内的声明。同样,该函数具有单个地址,而不管除外部定义之外出现的内联定义的数量。

总结:大多数 C 和 C++ 用户对内联的期望并不是他们得到的。它明显的主要目的是避免函数调用开销,是完全可选的。但是为了允许单独编译,需要放宽单一定义。

(标准中引用的所有重点。)


编辑2:一些注意事项:

  • 外部内联函数有各种限制。函数中不能有静态变量,也不能引用静态 TU 范围对象/函数。
  • 刚刚在VC++的“整个程序优化”上看到了这个,这是一个编译器自己做内联事情的例子,而不是作者。
于 2009-02-24T18:25:27.260 回答
5

内联声明的重要之处在于它不一定做任何事情。在许多情况下,编译器可以自由决定内联未声明的函数,以及链接声明为内联的函数。

于 2009-02-24T18:26:41.423 回答
5

一个例子来说明内联的好处。sinCos.h:

int16 sinLUT[ TWO_PI ]; 

static inline int16_t cos_LUT( int16_t x ) {
    return sin_LUT( x + PI_OVER_TWO )
}

static inline int16_t sin_LUT( int16_t x ) {
    return sinLUT[(uint16_t)x];
}

当进行一些繁重的数字运算并且您希望避免在计算 sin/cos 上浪费周期时,您将 sin/cos 替换为 LUT。

当您在没有内联的情况下编译时,编译器不会优化循环,输出 .asm 将显示以下内容:

;*----------------------------------------------------------------------------*
;*   SOFTWARE PIPELINE INFORMATION
;*      Disqualified loop: Loop contains a call
;*----------------------------------------------------------------------------*

当您使用内联编译时,编译器了解循环中发生的事情并将进行优化,因为它确切地知道发生了什么。

输出 .asm 将有一个优化的“流水线”循环(即,它将尝试充分利用所有处理器的 ALU 并尝试在没有 NOPS 的情况下保持处理器的流水线满)。


在这种特定情况下,我能够将我的性能提高大约 2 倍或 4 倍,这使我能够满足我的实时截止日期所需。


ps 我正在使用定点处理器......任何浮点运算,如 sin/cos 都会影响我的性能。

于 2009-02-24T21:27:42.253 回答
5

不应将内联用于大型函数的另一个原因是库。每次更改内联函数时,您可能会失去 ABI 兼容性,因为针对旧标头编译的应用程序仍然内联了旧版本的函数。如果将内联函数用作类型安全宏,则很有可能函数在库的生命周期中永远不需要更改。但对于大型函数,这很难保证。

当然,只有当函数是公共 API 的一部分时,此参数才适用。

于 2009-02-25T09:56:35.683 回答
4

使用指针函数时,内联无效。

于 2009-02-24T18:26:17.073 回答
3

内联在一种情况下是有效的:当您遇到性能问题时,使用真实数据运行分析器,并发现一些小函数的函数调用开销很大。

除此之外,我无法想象你为什么要使用它。

于 2009-02-24T19:07:13.200 回答
2

这是正确的。对大函数使用内联会增加编译时间,并且不会给应用程序带来额外的性能。内联函数用于告诉编译器无需调用即可包含函数,并且应该是重复多次的小代码。换句话说:对于大函数,调用的成本与自己的函数实现的成本相比是可以忽略不计的。

于 2009-02-24T18:26:58.443 回答
2

内联可用于小型且经常使用的函数,例如 getter 或 setter 方法。对于大型函数,不建议使用内联,因为它会增加 exe 大小。同样对于递归函数,即使您进行内联,编译器也会忽略它。

于 2009-02-24T18:27:30.907 回答
2

我主要使用内联函数作为类型安全的宏。很长一段时间以来,人们一直在谈论向 GCC 添加对链接时优化的支持,尤其是在 LLVM 出现之后。不过,我不知道它实际上已经实施了多少。

于 2009-02-24T18:28:30.030 回答
2

就我个人而言,我不认为你应该内联,除非你首先在你的代码上运行了一个分析器并且已经证明该例程存在一个可以通过内联部分缓解的重大瓶颈

这是 Knuth 警告过的另一个过早优化案例。

于 2009-02-24T19:20:23.713 回答
1
  1. inline仅作为提示。
  2. 仅在最近添加。因此仅适用于最新的标准兼容编译器。
于 2009-02-24T18:26:16.543 回答
1

内联函数应该是大约 10 行或更少,取决于您选择的编译器。

你可以告诉你的编译器你想要内联的东西..这取决于编译器。没有我知道编译器不能忽略的 -force-inline 选项。这就是为什么您应该查看汇编器输出并查看您的编译器是否确实内联了该函数,如果没有,为什么不呢?许多编译器只是默默地说“去你的!” 在这方面。

因此,如果:

静态内联 unsigned int foo(const char *bar)

.. 并没有改善静态 int foo() 的事情,它是时候重新审视你的优化(和可能的循环)或与你的编译器争论了。请特别注意首先与您的编译器争论,而不是与开发它的人争论。或者当您第二天打开收件箱时,您只是在商店中阅读大量不愉快的内容。

同时,当做一些东西(或试图做一些东西)内联时,这样做真的证明膨胀是合理的吗?您真的希望每次调用该函数时都扩展它吗?跳转成本高吗?,您的编译器通常正确 9/10 次,检查中间输出(或 asm 转储)。

于 2009-02-25T12:28:05.897 回答