C++ inline关键字详解

C++ inline关键字的学习笔记记录

SMJ
loading... read

⚠️ This post was last updated on December 22, 2019 and the content may be OUTDATED!

If you encounter any issues, please feel free to reachout to me!

当程序执行函数调用指令时,CPU 将存储该函数调用后指令的内存地址,将函数的参数复制到堆栈上,最后将控制权转移到指定的函数。然后,CPU 执行函数代码,将函数返回值存储在预定义的内存位置/寄存器中,并将控制权返回给调用函数。如果函数的执行时间少于从调用者函数到被调用函数(被调用者)的切换时间,则这可能会成为开销。对于大型函数和/或执行复杂任务的函数,与函数运行所花费的时间相比,函数调用的开销通常微不足道。但是,对于小型的常用功能,进行函数调用所需的时间通常比实际执行函数代码所需的时间多得多。对于小功能,由于小功能的执行时间少于切换时间,因此会产生开销。

C++提供了 inline 函数,以减少函数调用的开销。内联函数是在调用时在行中扩展的函数。调用内联函数时,将在内联函数调用时插入或替换内联函数的整个代码。替换由 C ++编译器在编译时执行。如果内联函数很小,则可以提高效率。

inline 函数看起来像函数,动作像函数,但是又比宏方便很多,调用 inline 函数省去了参数压栈、生成汇编语言的 CALL 调用、返回参数、执行 return 等过程所花费的额外开销。不过 inline 函数也有缺点:增加目标码的大小。因为 inline 函数的设计思想是对该函数的所有调用都用函数本身替换掉,和宏定义一样,但是比宏定义用起来要简单很多。而且 inline 的调试同样很难。

所以,inline 函数对于函数体积很小,却又频繁调用这样的函数来说,可能比函数调用产生的代码更少,但是效率却不可同日而语。有一点需要注意的是:**inline 只是对编译器提出一个申请,并不是强制命令。编译器可以忽略内联请求。**在以下情况下,编译器可能不会执行内联:

  • 如果函数包含循环。(对于 while,do-while)
  • 如果函数包含静态变量。
  • 如果函数是递归的。
  • 如果函数的返回类型不是 void,并且函数主体中不存在 return 语句。
  • 如果函数包含 switch 或 goto 语句。

inline 可以显示声明,也可以隐式声明。隐式声明方式是将函数定义在类定义之中(函数定义时没有 inline 关键字),编译器也会当作 inline 函数。另外,定义在类中的 friend 函数也会被隐式声明为 inline 函数。

显式声明的时候必须与函数定义体放在一起才能使函数成为内联,仅将 inline 放在函数声明前面不起任何作用。定义函数内联的语法为:

    inline return-type function-name(parameters)
    {
        // function code
    }

此外,inline 函数通常放在头文件中,因为大多数 build environments 在编译期进行 inlining,编译器必须知道函数什么样子才能把函数调用替换成函数本体。

大多数 virtual 函数不能 inlining:因为 virtual 知道运行时才知道调用哪个函数,而 inline 是在执行前进行替换。此外,编译器通常不会 inlining通过函数指针进行的调用,下面是一个实例:

    inline void f() {...}      // assume compilers are willing to inline calls to f
    void (*pf)() = f;          // pf points to f
    ...
    f();                      // this call will be inlined, because it's a "normal" call
    pf();                     // this call probably won't be, because it's through              // a function pointer

内联函数的优点:

  1. 不会发生函数调用开销。
  2. 调用函数时,还节省了 push / pop 变量在栈上的开销。
  3. 它还节省了从函数返回调用的开销。
  4. 内联函数时,可以使编译器对函数主体执行特定于上下文的优化。对于正常的函数调用,这种优化是不可能的。通过考虑调用上下文和被调用上下文的流程可以获得其他优化。
  5. 内联函数可能对于嵌入式系统有用(如果很小),因为内联函数所产生的代码少于函数调用的前导和返回。

内联函数的缺点:

  1. 内联函数中添加的变量消耗了额外的寄存器,在内联函数之后,如果要使用寄存器的变量编号增加,则它们可能会增加寄存器变量资源利用的开销。这意味着当在函数调用点替换内联函数主体时,该函数使用的变量总数也会被插入。因此,将用于变量的寄存器数量也将增加。因此,如果函数内联后的变量数急剧增加,则肯定会导致寄存器利用率增加。
  2. 如果使用太多的内联函数,则由于重复执行相同的代码,二进制可执行文件的大小将很大。
  3. 过多的内联也会降低指令 Cache 命中率,从而降低了从高速缓存到主存储器的指令获取速度。
  4. 如果有人更改了内联函数中的代码,则内联函数可能会增加编译时间开销,然后必须重新编译所有调用位置,这是因为编译器将需要再次替换所有代码以反映更改,否则它将继续使用旧功能。
  5. 内联函数对于许多嵌入式系统可能没有用。因为在嵌入式系统中,代码大小比速度更重要。
  6. 内联函数可能会导致崩溃,因为内联可能会增加二进制可执行文件的大小。内存溢出会导致计算机性能下降。

Note:

  • 将大多数内联限制在小的,经常调用的函数中。这有助于调试和二进制可升级性,最大程度地减少潜在的代码膨胀,并最大程度地提高程序速度。
  • 不要仅仅因为函数模板出现在头文件中就声明 inline。

Reference

Effective C++

https://www.geeksforgeeks.org/inline-functions-cpp/

Sooner or later, everything ends.