何时“内联”无效? (在C中)

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

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

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

谢谢你的回答!

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:几点说明:

  • 外部内联function有各种限制。 您不能在函数中使用静态变量,也不能引用静态TU范围对象/函数。
  • 刚刚在VC ++的“ 整个程序优化 ”中看到了这一点,这是一个编译器做自己的内联事件的例子,而不是作者。

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

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

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

一个例子来说明内联的好处。 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时浪费周期时,用LUT替换sin / cos。

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

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

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

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


在这个特定的情况下,我能够将我的表现提高大约2倍或4倍,这使我达到了我在实时截止日期所需的范围内。


ps我正在研究定点处理器…而像sin / cos这样的任何浮点运算都会影响我的性能。

使用指针运行时,内联无效。

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

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

那就对了。 对大函数使用内联会增加编译时间,并且几乎不会给应用程序带来额外的性能。 内联函数用于告诉编译器在没有调用的情况下包含函数,这应该是重复多次的小代码。 换句话说:对于大function,与自己的function实现的成本相比,进行调用的成本可以忽略不计。

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

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

这是Knuth过早优化的另一个例子。

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

  1. inline仅作为提示。
  2. 最近才添加。 因此只适用于最新的标准兼容编译器。

根据您选择的编译器,内联函数应大约为10行或更少。

您可以告诉您的编译器您想要内联的内容..由编译器来完成。 我不知道编译器不能忽略的-force-inline选项。 这就是为什么你应该看看汇编器输出,看看你的编译器是否实际内联函数,如果没有,为什么不呢? 许多编译器只是默默地说’搞砸你!’ 在这方面。

因此,如果:

static inline unsigned int foo(const char * bar)

..并没有改进静态int foo()的时间来重新审视你的优化(和可能的循环)或与你的编译器争论。 首先要特别注意与你的编译器争论,而不是那些开发它的人……或者当你第二天打开你的收件箱时,你只是在商店里看到很多不愉快的阅读。

与此同时,在内联制作某些东西(或试图制作某些东西)时,这样做是否真的certificate了这种膨胀是正当的? 你真的希望每次调用它时都能扩展这个function吗? 跳转是否如此昂贵?,您的编译器通常是正确的9/10次,检查中间输出(或asm转储)。