1.什么是内联函数
在函数声明或定义中,在函数的返回类型前加上C++关键字inline
,即将函数指定为内联函数。这样可以**解决一些频繁调用的简单函数大量消耗栈空间(栈内存)**的问题。关键字inline
必须与函数定义放在一起才能使函数成为内联函数,仅仅将inline
放在函数声明前面不起任何作用。inline
是一种“用于实现”的关键字,而不是一种“用于声明”的关键字。
2.为什么要使用内联函数
当程序执行函数调用时,有标准的函数调用过程,要建立栈空间,保护现场,参数压栈,函数栈帧的开辟和回退过程以及控制程序执行的转移等等,这些工作需要函数调用时间和空间的开销。
以下代码为例,当函数功能简单,使用频率很高时,为了提高效率,直接将函数的代码嵌入到程序中。但这个办法有缺点,一是编译期间相同的代码会重复写,二是程序的可读性往往没有使用函数的好。
#include <iostream>
using namespace std;
bool Isnumber(char ch)
{
return ch >= '0' && ch <= '9' ? 1 : 0;
}
int main()
{
char ch;
while (cin.get(ch), ch != '\n')
{
if (Isnumber(ch))
{
cout << "是数字字符" << endl;
}
else
{
cout << "不是数字字符" << endl;
}
}
return 0;
}
为了协调好效率和可读性之间的矛盾,C++提供了另一种方法,即定义内联函数,方法是在定义函数时用关键词inline
去修饰。
inline bool Isnumber(char ch)
{
return ch >= '0' && ch <= '9' ? 1 : 0;
}
加了关键词inline将其改为内联函数,在编译期间编译器能够在调用点内联展开该函数,这样在编译过程中就没有函数调用的开销了。
【注意】在debug版本中,inline是不起作用的,inline只有在release版本下才起作用
3.实例
(1)在debug模式下:
不是内联函数的情况:
改为内联函数之后的情况:
可以看出,在debug模式下,虽然使用了inline关键字,将其改为内联函数,但是在汇编代码中可以看到函数并没有被处理成内联函数,仍然存在函数调用过程中的函数压栈mov,push和call指令。
(2)在debug模式下,设置编译器:
不是内联函数的情况:
改为内联函数之后的情况:
可以发现不在debug模式下的时候,将函数改为内联函数之后,函数被处理成内联函数,在编译过程中,函数调用的开销就没有了,在函数调用点把被调用函数的代码直接展开处理了。
4.要点
(1)inline是一种以空间换时间的做法,省去调用函数的额外开销。但当函数体的代码过长或者是递归函数,即便加了inline关键字,也不会在调用函数点以内联展开该函数。
(2)inline对于编译器而言只是一个建议,编译器会自动优化。
(3)inline不建议声明和定义分离,分离会导致链接错误。因为inline被展开就没有函数地址了,链接就会找不到。
比如:
A.h
的代码如下:
A.cpp
的代码如下:
Maintest.cpp
的代码如下:
系统报错:
5.什么情况采用inline处理合适,什么情况采用普通函数处理合适
如果函数的执行开销小于开栈清栈的开销(函数体较小),使用inline处理效率高。
如果函数的执行开销大于开栈清栈的开销,使用普通函数方式处理。
6.总结
(1)inline与静态关键字static的区别:
内联函数没有开栈清栈的开销;而static函数有;
inline编译阶段代码展开导致函数本文件可见,而static是因为符号属性为local本文件可见。
(2)内联函数和宏定义的区别:
不同点:
内联函数在编译时展开,带参的宏在预编译时展开;
内联函数直接嵌入到目标代码中,带参的宏只是简单的做文本替换;
内联函数有类型检测、语法判断等功能,宏只是替换;
内联函数可以进行调试,宏无法调试;
内联比宏更加安全,是一种更加安全的宏。
相同点:
两个都代码展开,省去了参数压栈、栈帧开辟与回收、结果返回等,从而提高运行速度。
(3)内联函数的注意事项
inline一般写在头文件中;
只在release版本生效;
inline是给编译器的一个建议,而循环、递归、switch一定不会出现inline;
inline基于实现,而不是基于声明,即在声明点无效(先声明后内联)。