概述
inline函数简介
inline函数是由inline关键字定义的,引入inline函数的主要原因是用它代替C中复杂易错且不易维护的宏函数。
编译器对inline函数的处理方法
编译器在编译阶段完成对inline函数的处理。处理方式就是在调用inline函数处将其替换为inline函数的本体。从逻辑上说,包含下面四个步骤:
- 将inline函数体复制到inline函数调用点处(调用函数体内)。
- 为所用的inline函数中的局部变量分配内存空间。
- 将inline函数的输入参数和返回值映射到调用者的局部变量空间中。
- 如果inline函数有多个返回点,则将其转变为inline函数代码块末尾的分支(使用GOTO)。
示例代码:
// 求0~9的平方
inline int inlineFunc(int num)
{
if (num > 9 || num < 0)
return -1;
return num * num;
}
int main()
{
int a = 9;
int res = inlineFunc(a);
cout << "res:" << res << endl;
}
inline之后的main函数代码类似于如下形式:
int main()
{
int a = 0;
int res = 0;
{
int _temp_b = a;
int _temp;
if (_temp_b > 9 || _temp_b < 0) _temp = -1;
else _temp = _temp_b * _temp_b;
res = _temp;
}
...
}
经过上述处理,可消除所有与调用相关的痕迹以及性能的损失。inline通过消除调用开销来提升性能。
inline函数使用的一般方法
函数定义时,在返回类型的前面加上关键字inline即把函数指定为内联,函数声明时可加也可不加inline关键字。但是,建议在函数声明的时候也加上inline,这样能够达到"代码即注释"的作用。
使用格式如下:
inline int functionName(int first, int second, ...) {...}
inline如果只修饰函数声明的部分,那么如下风格的函数foo就不能成为内联函数:
inline void foo(int x, int y); // inline仅与函数声明放在一起
void foo(int x, int y){...}
而如下风格的函数foo却能成为内联函数:
void foo(int x, int y);
inline void foo(int x, int y) {...} // inline与函数定义放在一起
inline函数的优点
既然inline被设计的目的是用于替代宏函数,自然要与宏函数进行对比:
- inline函数同宏函数一样,将在被调用处进行代码展开,省去了参数压栈、栈帧开辟与回收、结果返回等操作,从而提高了程序运行速度。
- inline函数相比宏函数来说,在代码展开时会做类型安全检查或自动类型转换,而宏定义则不会。
举个例子:
// 宏函数
#define MAX(a, b) ((a) > (b) ? (a) : (b))
// inline函数
inline int MAX(int a, int b) { return a > b ? a : b;}
使用宏函数时,其书写语法比较苛刻,容易出错,比如上述宏定义写成了如下格式就容易在调用时达不到效果:
#define MAX(a, b) a > b ? a : b
如果进行调用写成了MAX(1, "Hello")
,宏函数会错误地比较int和string类型,运行时可能会发生崩溃,而inline函数会在编译阶段进行类型检查,从而避免了这种错误。
- 在类中声明并同时定义的成员函数,将自动转换为内联函数,因此内联函数可以访问类的成员变量,而宏函数则不行。
- 内联函数在运行时可调试,而宏函数则不可以。
inline函数的缺点
-
代码膨胀
inline函数带来的运行效率提升是典型的以空间换时间的做法。内联是以代码膨胀作为代价,以消除函数调用带来的开销。如果执行函数体内代码的时间相比于函数调用的开销大,那么效率的收获会很小。另外,每一处内联函数的调用都是以代码复制方式,将使生成后可执行文件体积变大,消耗更多的内存空间(程序运行时需要将整个代码加到内存空间中)。
-
inline函数无法随着函数库的升级而升级
如果f函数采用内联方式编译,那么调用者就会把函数实体复制到调用处,一旦函数库发生了改变,就需要重新编译,提供头文件和dll。但如果采用no-inline方式,那么只需要提供dll库就可以了。
-
是否内联,程序员无法控制
inline关键字对编译器而言只是一种建议,编译器可以采用,也可以不采用。如果编译器认为若调用某函数的开销相对该函数本身的开销微不足道,或者不足以承担代码膨胀的后果,则没必要内联该函数。某些编译器也不支持递归函数的内联。
使用内联函数的一些注意事项
-
使用函数指针调用内联函数将会导致内联失败
也就是说,如果使用函数指针来调用内联函数,就需要获取inline函数的地址。如果要获取inline函数的地址,编译器就需要为此函数产生一个函数实体,那么就内联失败。
-
如果函数体代码过长或者有多重循环语句、if或者switch分支语句或递归,则不宜用内联。
-
类的构造函数、析构函数和虚函数往往不是inline函数的最佳选择
类的构造函数可能需要调用基类的构造函数,析构函数同样可能需要调用基类的析构函数,二者背后隐藏着大量的代码,不适合作为inline函数。虚函数往往是运行时确定的,而inline函数是在编译时进行的,所以内联虚函数往往无效。
-
如果inline函数被多个源文件调用,那么就必须把它定义在头文件中
C++编译采用的是独立编译模式,每个头文件和对应的源文件都是一起编译成.obj文件的。在引用到inline函数的头文件中,如果inline定义在了源文件中,就无法完成函数体替换作用,在链接时就找不到对应的inline函数的定义,产生链接错误。
-
inline函数对于编译器来说不是强制的,即使采用了__forceinline也是达不到内联编译的效果
-
如何查看函数是否被内联处理
既然内联的方式是采用代码替换的模式,可以对编译后的文件进行反汇编查看,看看是否进行了函数调用替换处理(call)。如果不太懂的话,可以参考博文《内联函数到底有没有被嵌入到调用处呢?》
-
关于在类中定义的函数为什么没有包重复定义的错误
类函数成员定义在类体内并随着类的定义放在头文件中,当被不同的源文件包含时,每个源文件都应该包含类成员函数的实体,为什么在链接的过程中不会报函数的重定义错误呢?
原因是在链接时,链接器会对重复的成员函数实体进行冗余优化,只保留一份函数实体,也就不会产生重定义的错误了。除了inline函数外,C++编译器在很多时候都会产生重复的代码,比如模板、虚函数表、类的默认成员函数(构造函数、析构函数和赋值运算符)等。以函数模板为例,在多个源文件生成相同的实例,链接时不会出现函数重定义的错误,实际上是一个道理,因为链接器会对重复代码进行删除,只保留一份函数实体。
总结
可以将内联理解为C++中对于函数专有的宏,对于C中函数宏的一种改进。对于常量宏,C++中以const代替;而对于函数宏,C++提供的方案则是inline。C++的内联机制,既具备宏代码的效率,又增加了安全性,还可以自由操作类的数据成员,算是一种比较完美的解决方案。
最后
以上就是昏睡裙子为你收集整理的C++进阶教程 - inline函数使用总结的全部内容,希望文章能够帮你解决C++进阶教程 - inline函数使用总结所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复