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

时间:2009-02-24 18:21:50

标签: c inline

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

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

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

感谢您的回答!

12 个答案:

答案 0 :(得分:26)

inline做了两件事:

  1. 允许您免除“一个定义规则”(见下文)。此始终适用。
  2. 为编译器提供一个提示,以避免函数调用。编译器可以自由地忽略它。
  3. #1非常有用(例如,如果短,则将标题放在标题中),即使#2被禁用。

    在实践中,编译器通常会更好地确定自己内联的内容(特别是如果可以使用配置文件引导优化)。


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

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

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

      

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

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

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

      

    使函数成为内联函数表明对函数的调用为as   尽可能快。 [118] 这些建议有效的程度如下   实现定义的。

    脚注(非规范性),但提供了澄清:

      

    例如,通过使用常用函数调用机制的替代方法,例如“内联替换”。内联替换不是文本替换,也不是创建新函数。因此,例如,在函数体内使用的宏的扩展使用它在函数体出现时的定义,而不是调用函数的位置;和标识符指的是身体发生范围内的声明。同样,该函数只有一个地址,无论外部定义发生的内联定义的数量如何。

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

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


    编辑2:一些注释:

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

答案 1 :(得分:5)

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

答案 2 :(得分: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时浪费周期时,用LUT替换sin / cos。

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

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

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

输出.asm将有一个优化的“流水线”循环(即它将尝试充分利用所有处理器的ALU并尝试保持处理器的管道完整而没有NOPS。)


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


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

答案 3 :(得分:5)

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

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

答案 4 :(得分:4)

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

答案 5 :(得分:3)

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

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

答案 6 :(得分:2)

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

答案 7 :(得分:2)

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

答案 8 :(得分:2)

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

答案 9 :(得分:2)

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

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

答案 10 :(得分:1)

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

答案 11 :(得分:1)

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

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

所以如果:

static inline unsigned int foo(const char * bar)

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

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