对C/C++中inline的思考

Last Updated: 2024-01-02 11:09:39 Tuesday

-- TOC --

inline这个关键词,是从C99开始出现的。它要解决的问题很明确,将函数在调用处展开,去掉函数调用的开销(调用栈相关操作),提高代码执行速度。

不过inline只是对compiler的一个建议,会影响compiler是否做inline的决策,但不是强制!现代编译器,在开启优化之后,也会自动将一些non-inline申明的函数进行inline优化。

独立函数接口的优缺点

int max(int a, int b){
    return a > b ? a : b;
}

为这么一个小小的操作定义一个函数的好处有:

虽然有这么多好处,但是写成函数有一个潜在的缺点:调用函数比求解等价表达式要慢得多。在大多数的机器上,调用函数都要做很多工作:调用前要先保存寄存器,并在返回时恢复,复制实参,程序还必须转向一个新位置执行。

static inline function

这是最纯粹的inline用法,我们始终要记住,C语言的编译是按文件进行的,每个.c文件单独编译,最后链接在一起。既然是单独编译,要发挥inline的作用,定义为inline的函数接口,必然要与caller在同一个编译单元中。static inline的语义,就是先static,此函数是个局部符号,只在当前文件可见,然后inline,在调用处展开。

另外,inline要出现在函数定义(definition)的地方,出现在申明(declaration)的地方没有意义!如果一个函数是static inline,就直接定义了,没有必要先申明,在申明的位置出现inline反而会有编译错误。

inline函数是一种用于实现的关键字,而不是一种用于声明的关键字。inline不应该出现在函数的声明中。这个细节虽然不会影响函数的功能,但是体现了高质量C/C++程序设计风格的一个基本原则:声明与定义不可混为一谈,用户(程序员)没有必要、也不应该知道函数是否需要内联。

inline对于编译器而言,只是个建议,不是强制。如下两种情况,编译器会忽略掉inline:

  1. 函数地址被使用的时候。如通过函数指针对函数进行了间接调用,这种情况下就必须为此函数接口生成独立汇编,否则它没有自己的地址。
  2. 其他一些无法展开的情况,比如函数本身有递归调用自身的行为等。

其实,对于以上两种情况,程序员就应该意识到,不能使用inline!

inline function

对于gcc而言,仅有inline属性无static的函数,在当前文件内部,其含义与static inline一样,可能会在调用处展开。而对于外部文件的调用(没有static修饰,就可以是extern函数),跟普通全局函数一样。

因此,gcc实际上在只有inline无static的时候,一定会为函数生成一份独立的汇编码,以便被别的模块调用。在别的文件中,这个函数跟其它全局函数没有区别。

extern inline function

这是不建议使用的申明外部函数接口的方式,没有什么意义,去掉extern inline,只是申明function prototype即可。

inline与gcc优化参数

inline关键字仅仅是建议编译器做内联展开处理,而不是强制。

gcc编译器默认的编译优化为-O0,此时:

强制内联用__attribute__((always_inline))属性,在性能优化时可以考虑使用。

要让inline有效果,至少-O1!

将inline函数放在头文件中

内联函数应该在头文件中定义,这一点不同于其他函数。编译器在调用点内联展开函数的代码时,必须能够找到 inline函数的定义才能将调用函数替换为函数代码,而对于在头文件中仅有函数声明是不够的。

想想C++头文件中的inline variable...统一了...

inline一定会让代码size变大吗?

不一定。

短小精干的函数,其编译之后的size可能比函数调用过程代码的size还要小,这种情况就不会增加整体size。

inline一定会加速代码执行吗?

不一定。

Google编程规范之inline

只有当函数小于10行时,才将其做inline申明。

当函数体比较小的时候, 内联该函数可以令目标代码size更小,执行更加高效。对于存取函数以及其它函数体比较短, 性能关键的函数, 鼓励使用内联。

滥用内联将导致程序变慢。内联可能使目标代码size或增或减, 这取决于内联函数的大小。内联非常短小的存取函数通常会减少代码大小, 但内联一个相当大的函数将戏剧性的增加代码size。现代处理器由于更好的利用了CPU指令缓存, size小的代码往往执行更快。

一个较为合理的经验准则是, 不要内联超过10行的函数。谨慎对待析构函数, 析构函数往往比其表面看起来要更长, 因为有隐含的成员和基类析构函数被调用!

另一个实用的经验准则: 内联那些包含循环或switch语句的函数常常是得不偿失 (因为它们的汇编代码有比较多的判断和跳转,代码size其实不算小)。

有些函数即使声明为内联的也不一定会被编译器内联, 这点很重要,比如虚函数和递归函数就不会被内联。通常, 递归函数不应该声明成内联函数(递归调用栈的展开并不像循环那么简单, 比如递归层数在编译时可能是未知的, 大多数编译器都不支持内联递归函数)。

C++ function template

In general, function template don't have to be declared with inline!

本文链接:https://cs.pynote.net/sf/c/202112212/

-- EOF --

-- MORE --