C++中的内联函数详细指南

2021年3月17日18:38:45 发表评论 815 次浏览

内联函数是C ++的重要功能之一。因此, 首先让我们了解为什么使用内联函数, 以及内联函数的目的是什么?

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

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

定义函数内联的语法为:

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

请记住, 内联只是对编译器的请求, 而不是命令。编译器可以忽略内联请求。在以下情况下, 编译器可能不会执行内联:

1)如果函数包含循环。 (用于一段时间)

2)如果函数包含静态变量。

3)如果函数是递归的。

4)如果函数的返回类型不是void, 并且函数主体中不存在return语句。

5)如果函数包含switch或goto语句。

内联函数具有以下优点:

1)不会发生函数调用开销。

2)调用函数时, 还可以节省堆栈中push / pop变量的开销。

3)它还节省了从函数返回调用的开销。

4)内联函数时, 可以使编译器对函数主体执行特定于上下文的优化。对于正常的函数调用, 这种优化是不可能的。通过考虑调用上下文和被调用上下文的流程可以获得其他优化。

5)内联函数可能对于嵌入式系统有用(如果很小), 因为内联函数所产生的代码少于函数调用的前导和返回。

内联函数的缺点:

1)内联函数中添加的变量会占用额外的寄存器, 在内联函数之后, 如果要使用寄存器的变量编号增加, 则它们可能会增加寄存器变量资源利用率。这意味着当在函数调用点替换内联函数主体时, 也会插入该函数使用的变量总数。因此, 将用于变量的寄存器数量也将增加。因此, 如果函数内联后的变量数急剧增加, 则肯定会导致寄存器利用率增加。

2)如果使用过多的内联函数, 则由于重复执行相同的代码, 二进制可执行文件的大小将很大。

3)过多的内联还会降低指令高速缓存的命中率, 从而降低了从高速缓存到主存储器的指令获取速度。

4)如果有人更改了内联函数中的代码, 则内联函数可能会增加编译时间开销, 然后必须重新编译所有调用位置, 因为编译器需要再次替换所有代码以反映更改, 否则它将继续使用旧功能。

5)内联功能对于许多嵌入式系统可能没有用。因为在嵌入式系统中, 代码大小比速度更重要。

6)内联函数可能会导致崩溃, 因为内联可能会增加二进制可执行文件的大小。内存溢出会导致计算机性能下降。

下面的程序演示了如何使用内联函数。

#include <iostream>
using namespace std;
inline int cube( int s)
{
     return s*s*s;
}
int main()
{
     cout << "The cube of 3 is: " << cube(3) << "\n" ;
     return 0;
} //Output: The cube of 3 is: 27

内联函数和类:

也可以在类内部定义内联函数。实际上, 该类内部定义的所有函数都是隐式内联的。因此, 这里也应用了内联函数的所有限制。如果你需要在类中显式声明内联函数, 则只需在类内部声明该函数, 然后使用inline关键字在类外部对其进行定义。

例如:

class S
{
public :
     inline int square( int s) // redundant use of inline
     {
         // this function is automatically inline
         // function body
     }
};

上面的样式被认为是不好的编程样式。最好的编程风格是只在类内部编写函数原型, 并将其指定为函数定义中的内联。

例如:

class S
{
public :
     int square( int s); // declare the function
};
  
inline int S::square( int s) // use inline prefix
{
  
}

下面的程序演示了此概念:

#include <iostream>
using namespace std;
class operation
{
     int a, b, add, sub, mul;
     float div ;
public :
     void get();
     void sum();
     void difference();
     void product();
     void division();
};
inline void operation :: get()
{
     cout << "Enter first value:" ;
     cin >> a;
     cout << "Enter second value:" ;
     cin >> b;
}
  
inline void operation :: sum()
{
     add = a+b;
     cout << "Addition of two numbers: " << a+b << "\n" ;
}
  
inline void operation :: difference()
{
     sub = a-b;
     cout << "Difference of two numbers: " << a-b << "\n" ;
}
  
inline void operation :: product()
{
     mul = a*b;
     cout << "Product of two numbers: " << a*b << "\n" ;
}
  
inline void operation ::division()
{
     div =a/b;
     cout<< "Division of two numbers: " <<a/b<< "\n" ;
}
  
int main()
{
     cout << "Program using inline function\n" ;
     operation s;
     s.get();
     s.sum();
     s.difference();
     s.product();
     s.division();
     return 0;
}

输出如下:

Enter first value: 45
Enter second value: 15
Addition of two numbers: 60
Difference of two numbers: 30
Product of two numbers: 675
Division of two numbers: 3

宏有什么问题?

熟悉C语言的读者知道C语言使用宏。预处理程序直接在宏代码内替换所有宏调用。建议始终使用内联函数而不是宏。根据C ++的创建者Bjarne Stroustrup博士所说, 在C ++中, 宏几乎从不需要, 而且容易出错。在C ++中使用宏存在一些问题。宏无法访问班级的私人成员。宏看起来像函数调用, 但实际上不是。

例子:

#include <iostream>
using namespace std;
class S
{
     int m;
public :
#define MAC(S::m)    // error
};

C ++编译器检查内联函数的参数类型, 并且正确执行了必要的转换。预处理程序宏无法执行此操作。另一件事是, 宏由预处理器管理, 内联函数由C ++编译器管理。

切记:确实, 该类内部定义的所有函数都是隐式内联的, 并且C ++编译器将对这些函数执行内联调用, 但如果该函数是虚拟的, 则C ++编译器将无法执行内联。原因是对虚拟函数的调用是在运行时而不是在编译时解决的。虚拟方式要等到运行时才执行, 而内联方式要等到编译期间, 如果编译器不知道将调用哪个函数, 它如何执行内联?

要记住的另一件事是, 仅当函数调用过程中花费的时间比函数主体执行时间多时, 才使函数内联。内联函数完全无效的示例:

inline void show()
{
     cout << "value of S = " << S << endl;
}

上面的函数执行起来相对要花很长时间。通常, 执行输入输出(I / O)操作的功能不应定义为内联函数, 因为它会花费大量时间。从技术上讲, 内联show()函数的值是有限的, 因为I / O语句将花费的时间远远超过了函数调用的开销。

如果函数未内联扩展, 则取决于使用的编译器, 编译器可能会向你显示警告。 Java和C#等编程语言不支持内联函数。

但是在Java中, 编译器可以在调用较小的final方法时执行内联, 因为final方法不能被子类覆盖, 并且对final方法的调用在编译时即可解决。在C#中, JIT编译器还可以通过内联小的函数调用来优化代码(例如, 在循环中替换小函数的主体时)。

最后要记住的是, 内联函数是C ++的宝贵功能。适当地使用内联函数可以提高性能, 但是如果任意使用内联函数, 则它们无法提供更好的结果。换句话说, 不要指望程序会有更好的性能。不要使每个函数都内联。最好使内联函数尽可能小。

参考文献:

1)

有效的C ++, 斯科特·迈耶斯(Scott Meyers)

2)

http://www.parashift.com/c++-faq/inline-and-perf.html

3)

http://www.cplusplus.com/forum/articles/20600/

4)

用C ++思考, 第1卷, 布鲁斯·埃克尔(Bruce Eckel)

.

5)

C ++完整参考, Herbert Schildt

本文作者:认识普拉瓦西。如果发现任何不正确的地方, 或者想分享有关上述主题的更多信息, 请写评论。

被认为是行业中最受欢迎的技能之一, 我们拥有自己的编码基础C ++ STL通过激烈的问题解决过程来训练和掌握这些概念。

木子山

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: