内联函数详解(搞清内联的本质及其用法)
一、为什么要使用内联函数
在函数定义前加上关键字inline
inline void print(char *s)
{
printf("%s",s);
}
二、为什么要使用内联函数
最初目的:代替部分#define宏定义
使用内联函数替代普通函数的目的:提高程序的运行效率
1.为什么要代替部分宏定义
a.宏是预处理指令,在预处理的时候把所有的宏名用宏体来替换;
内联函数是函数,在编译阶段把所有调用内联函数的地方把内联函数插入
b.宏没有类型检查,无论是对还是错都是直接替换;而内联函数在编译时进行安全检查
c.宏的编写有很多限制,只能写一行,不能使用return控制流程
d.对于C++而言,使用宏代码还有另一种缺点:无法操作类的私有数据成员
2.普通函数频繁调用的过程消耗栈空间
函数调用是有时间和空间开销的。程序在执行一个函数之前需要做一些准备工作,要将实参、
局部变量、返回地址以及若干寄存器都压入栈中,然后才能执行函数体中的代码;函数体中的
代码执行完毕后还要清理现场,将之前压入栈中的数据都出栈,才能接着执行函数调用位置以后的代码。
为了消除函数调用的时空开销,C++提供一种提高效率的方法,即在编译时将函数调用处用函数体替换,
类似于C语言的宏展开。这种在函数调用处直接嵌入函数体的函数即为内联函数。缺点是增加了代码的重复量。
三、内联函数和编译过程的相爱相杀
1.函数前面加上inline一定会有效果嘛?
不会,使用内联inline关键字修饰函数只是一种提示,编译器不一定会认
2.如果不加inline就不是内联函数了嘛?
存在隐士内联,不用inline关键字,C++在类内定义的所有函数都自动称为内联函数
3.内联函数一定会展开嘛?
不一定,同一
4.在什么情况下内联函数会展开?
首先需要满足有inline修饰或者是类中的定义的函数,然后再由编译器决定
内联函数管不管用由编译器说了算**********
如何要求编译器展开内联函数?
1.编译器开优化:
gcc-O2 test.c -o test 只有在编译器开启优化选项的时候,才会有inline行为的存在。
g++ -O0 不会作任何的内联处理
g++ -O2 编译器会通过启发式算法决定是否值得对一个函数进行内联,同时要保证不会对生成文件的大小产生较大的影响
g++ -O3 不再考虑生成文件的大小
2.使用attribute属性:
static inline attribute((always_inline)) int add_i(int a,int b);
3.使用auto_inline:
#pragma auto_inline(on/off),当使用#pragma auto_inline(off)指令时,会关闭对函数的inline处理,
这时即使在函数前面加了inline指令,也不会对函数进行内联处理
上述三个操作仅仅对编译器提出内联的建议,最终是否进行内联由编译器自己决定,大多数编译器会拒绝比较
复杂的内联函数(包含循环或者递归的),而且类的构造函数、析构函数和虚函数往往不是内联函数的最佳选择
四、内联函数怎么用,在哪儿用?
1.内联函数是定义在头文件还是源文件?
定义在头文件
a.隐式内联
c++在类内定义的所有函数都自动称为内联函数,类的成员函数的定义直接写在类的声明中,不需要inline关键字
例如:
#include <stdio.h>
class Trace{
public:
Trace()
{
noisy = 0;
}
void print(char *s)
{
if (noisy)
{
printf("%s", s);
}
}
void on(){ noisy = 1; }
void off(){ noisy = 0; }
private:
int noisy;
};
b.显式内联:需要使用inline关键字
#include <stdio.h>
class Trace{
public:
Trace()
{
noisy = 0;
}
void print(char *s); //类内没有显示声明
void on(){ noisy = 1; }
void off(){ noisy = 0; }
private:
int noisy;
};
//类外显示定义
inline void Trace::print(char *s)
{
if (noisy)
{
printf("%s", s);
}
}
五、内联函数与重定义
1.什么是重定义?
C/C++语法中,如果变量、函数在同一个工程中被多次定义,链接期间会报类似“对 xxx 多重定义”的错误。
当内联函数的声明和定义分别在头文件和源文件中,并且在其他文件中被调用时,链接期间编译器会报
“对 xxx 未定义的引用”错误。内联函数如果会在多处被调用,则需要将函数的定义写在头文件中。
2.为什么inline关键字修饰的函数定义在头文件中(函数可能会被多次定义),编译器不会报“对XXX多重定义的错误”?
编译器对被inline修饰的函数做了特殊处理,inline起到了内联的作用
3.inline是一个弱符号?
弱符号:在C语言中,编译器默认函数和初始化了的全局变量为强符号,未初始化的全局变量为弱符号。
强符号之所以强,是因为其有确切的数据,变量有值,函数有函数体;
弱符号之所以弱,是因为还未被初始化,没有确切的数据
链接器会按照如下的规则处理被多次定义的强符号和弱符号:
a.不允许强符号被多次定义,也即不同的目标文件中不能有同名的强符号,如果有多个强符号,那么链接器会报符号重复定义错误
b.如果一个符号在某个目标文件中是强符号,在其它文件中是弱符号,那么选择强符号
c.如果一个符号在所有的目标文件中都是弱符号,那么选择其中占用空间最大的一个
六、内联的使用建议
使用:
1.对程序执行性能有要求时,适当使用内联函数
2.宏定义一个函数时,使用内联函数
3.写一些功能专一且性能关键的函数,函数体不大,包含了很少的执行语句。通过inline声明,编译器不需要
跳转到内存其它地址去执行函数调用,也不需要保留函数调用时的现场数据
4.在类内部定义的函数会默认声明为inline函数,这有利于类实现细节的隐藏(但也需要斟酌如果不需要隐藏的时候,其实大部分是不推荐默认inline的)
不使用:
1.如果函数体内的代码比较长,使用内联将导致内存消耗代价较高
2.如果函数体内出现循环或者开关语句,那么执行函数体内代码的时间要比函数调用的开销大。
七、内联与static
多数函数会把static与inline放在一起使用,但是谨慎使用static,使用inline即可