C++教程-C++中的内联函数(Inline Function)

C++中的内联函数(Inline Function)
C++的一个关键特性是内联函数(inline function)。因此,让我们首先了解内联函数的使用和预期应用。如果将函数声明为内联函数,在编译时编译器会将函数调用位置替换为内联函数的定义。
对内联函数进行任何更改都需要重新编译内联函数,因为编译器需要将所有代码替换为新代码,否则将执行旧功能。
简而言之,当程序执行函数调用指令时,CPU将函数的参数复制到堆栈中,缓存下一条指令的内存地址,然后将控制权交给目标函数。CPU执行函数的代码,将返回值保存在指定的内存地址或寄存器中,并将控制权返回给调用函数。如果函数的执行时间短于从调用函数到被调用函数的切换时间,则这可能成为开销。与运行大型或复杂函数所需的时间相比,函数调用的开销通常是可以忽略的。然而,小型经常使用的函数的调用时间往往远远长于运行函数代码所需的时间。由于它们的执行时间小于切换时间,小型函数会遇到这种开销。为了最小化函数调用的开销,C++提供了内联函数。当调用内联函数时,它会在内联函数调用的位置处展开其完整的代码体。C++编译器在编译时进行此替换。如果内联函数很小,则内联函数可以提高效率。对编译器而言,内联仅仅是一个请求;它不是一条命令。编译器可以拒绝内联请求。编译器可能不会在以下情况下实现内联:
- 如果函数包含循环(for、while、do-while)。
- 如果函数具有静态变量。
- 如果函数递归。
- 如果函数体中缺少返回语句且函数的返回类型不是void。
- 如果函数使用了goto或switch语句。
内联函数的语法:
inline 返回类型 函数名(参数)
{
// 函数代码
}
让我们理解普通函数和内联函数之间的区别。
在main()方法中,当调用fun1()函数时,控制权被转移到被调用函数的定义。函数被调用的地址和函数定义的地址是不同的。这个控制转移需要很多时间,增加了开销。
当遇到内联函数时,函数的定义会被复制到调用位置。在这种情况下,没有控制转移,这节省了很多时间并减少了开销。
让我们通过一个例子来理解。
#include <iostream>
using namespace std;
inline int add(int a, int b)
{
return(a+b);
}
int main()
{
cout<<"Addition of 'a' and 'b' is:"<<add(2,3); return 0;
}
编译完成后,代码将如下所示:
#include <iostream>
using namespace std;
inline int add(int a, int b)
{
return(a+b);
}
int main()
{
cout<<"Addition of 'a' and 'b' is:"<<return(2+3); return 0;
}
为什么我们需要C++中的内联函数?
在C++中,内联函数的主要用途是节省内存空间。每当调用函数时,执行任务(例如转到调用函数)需要很长时间。如果函数的长度很小,则会在此类开销中花费大量执行时间,有时前往调用函数所需的时间大于执行该函数所需的时间。
解决这个问题的方法是使用称为宏定义的宏。预处理器宏在C中被广泛使用,但宏的主要缺点是它们不是正常的函数,这意味着在编译过程中不会进行错误检查。
C++提供了一种解决方案。在函数调用的情况下,调用这样的小函数的时间很长,因此为了解决这个问题,引入了一个称为内联函数的新概念。当遇到函数时,它会与其定义一起展开,从而节省时间。
在以下情况下,我们不能对函数进行内联:
- 如果函数是递归的。
- 如果函数包含循环,例如for、while、do-while循环。
- 如果函数包含静态变量。
- 如果函数包含switch或goto语句。
什么时候需要使用内联函数?
内联函数可以在以下情况下使用:
- 当需要性能时可以使用内联函数。
- 可以替代宏定义使用内联函数。
- 可以在类外部使用内联函数,以隐藏函数的内部实现。
内联函数的优点
- 在内联函数中,不需要调用函数,因此不会引起任何开销。
- 它还节省了从函数返回的开销。
- 它不需要任何栈来推送或弹出变量,因为它不执行任何函数调用。
- 对于嵌入式系统而言,内联函数比普通函数产生的代码更少,因此对嵌入式系统非常有益处。
内联函数的缺点
- 在内联函数内创建的变量会消耗额外的寄存器。如果变量增加,寄存器的使用也会增加,这可能增加寄存器变量资源利用率的开销。也就是说,当将函数调用替换为内联函数体时,变量的数量也会增加,导致寄存器的数量增加。这会增加资源利用率的开销。
- 如果使用许多内联函数,那么二进制可执行文件也会变大。
- 使用过多的内联函数可能会降低指令缓存的命中率,降低从缓存内存到主内存的指令获取速度。 它还增加了编译时的开销,因为每当在内联函数内部进行更改时,代码需要重新编译以反映这些更改;否则,它将执行旧的功能。
- 有时,内联函数对许多嵌入式系统并不实用,因为在某些情况下,嵌入式系统的大小被认为比速度更重要。
- 由于二进制可执行文件大小的增加,它还可能导致抖动。如果抖动发生在内存中,则会导致计算机性能下降。
宏的问题是什么?
熟悉C的读者都知道它使用宏。在宏代码中,所有直接的宏调用都会被预处理器替换。建议始终使用内联函数而不是宏。C++的发明者Bjarne Stroustrup博士表示,在C++中很少需要使用宏,而且宏容易出错。在C++中使用宏并不是没有问题的。私有类成员对宏是不可访问的。虽然宏类似于函数调用,但它们并不是真正的函数调用。
例如:
#include <iostream>
using namespace std;
class S
{
int m;
public:
\#define MAC(S::m) // error
};
C++编译器会验证必要的转换是否完成,并验证内联函数的参数类型是否有效。预处理器宏无法完成这些操作。此外,预处理器管理宏,而C++编译器管理内联函数。请记住,虽然所有在类中指定的函数都会被隐式地内联,并且C++编译器将执行对这些函数的内联调用,但如果函数是虚函数,则无法进行内联。原因是虚函数的调用在运行时而不是在编译时解析。如果编译器不知道将调用哪个函数,那么如何在编译期间进行内联处理,而虚函数意味着等待运行时呢?还需要记住的是,只有在调用函数所需的时间长于执行函数体所需的时间时,将函数内联化才会有益处。举个例子:
例如:
inline void show()
{
cout << "value of S = " << S << endl;
}
执行上述函数需要一段时间。基本规则是,执行输入输出(I/O)操作的函数不应定义为内联函数,因为它需要很长时间。由于I/O语句所需的时间要比函数调用长得多,从技术上讲,内联化show()函数只有有限的效用。
如果函数没有展开为内联函数,根据所使用的编译器,可能会显示警告。内联函数不能在Java或C#等编程语言中使用。
但是,由于final方法不能被子类重写,并且对final方法的调用在编译时处理,因此当调用小的final过程时,Java编译器可以进行内联。通过内联短的函数调用,JIT编译器可以进一步优化C#代码(例如,在循环中调用时替换小函数的函数体)。