inline,一个神奇的关键字。有了它,你同时就可以获取函数和宏的优点。inline定义的函数,比起没有inline的函数来说,没有执行函数调用所带来的负担(对此可参见《C++程序的内存布局》),因此它是高效率的;比起宏来,它具有函数的可预期行为和参数类型检验。宏的行为难于预期,我们看看下面这个宏定义
#define max(a, b) ( (a) > (b) ? (a) : (b) )
int a = 5, b = 0;
max(++a, b); // a = a + 2
max(++a, b+10); // a = a + 1
如果这样:
inline int max(int a, int b)
{
return a > b ? a : b;
}
int a = 5, b = 0;
max(++a, b); // a = a + 1
max(++a, b+10); // a = a + 1
一切都很美好!但是会这么简单吗?
C++最初引入inline的原因是不想破坏类的封装,同时保持高效率。例如:
class stack {
private:
int i;
public:
int get() {return i;} // inline函数
};
想访问stack的成员变量i,想保持stack的封装,同时还想调用时高效率,那么请inline。
inline对于编译器而言,意味着“在编译阶段,将调用动作以被调用函数的本体替换之”。但是它只是一种建议,编译器可以去做,也可以不去做。从逻辑上来说,编译器将函数inline的步骤如下:
1、将inline函数体复制到inline函数调用点处;
2、为所用inline函数中的局部变量分配内存;
3、将inline函数的的输入参数和返回值映射到调用方法的局部变量空间中;
4、如果inline函数有多个返回点,将其转变为inline函数代码块末尾的分支(使用GOTO)。
经过以上处理,可消除所有与调用相关的痕迹以及性能的损失。inline通过消除调用开销来提升性能,并且允许进行调用间优化。我们看下面这段代码:
int test(){
int a = 6;
...... // 此处省略代码未对a经行修改
int b = inline_func(b);
...... // 此处省略代码未对b经行修改
int c = b + 1;
......
}
inline int inline_func(int q) {
if (q > 10) return -1;
else if (q > 0) return (1 << q) - 1;
else return 0;
}
inline后
int test() {
int a = 6;
...... // 此处省略代码未对a经行修改
int b;
{
int _temp_q = 6;
int _temp;
if (_temp_q > 10) _temp = -1;
else if (_temp_q > 0) _temp = (1 << q) - 1;
else _temp = 0;
b = _temp;
}
...... // 此处省略代码未对b经行修改
int c = b + 1;
......
}
优化后
int test(){
int a = 6;
...... // 此处省略代码未对a经行修改
int b = 0x3f;
...... // 此处省略代码未对b经行修改
int c = 0x40;
......
}
上面我们主要说了inline函数的优点,那么inline函数的缺点有哪些呢?我们来看看:
1、代码膨胀。如果inline函数体过大且编译器还让它inline成功,那么你最终的程序会代码膨胀,从而造成设备缓冲命中率低,引起较多的页面错误,读写硬盘的次数增多,这样程序的性能就下降了!建议:inline函数体一般不要超过5行,不包括循环,不包括递归调用。
2、inline函数内部不要有static变量。inline函数的定义几乎总是放在头文件(.h)里,这允许多个实现文件(.cpp)得以引用。我们知道编译器是分别编译的,所以这个时候,在多个实现文件里就会有多个inline函数的展开,也就是说有个多个static变量,这恐怕不是我们期望的!
3、inline函数无法随着函数库升级而升级。如果f是函数库中的一个inline函数,使用它的用户会将f函数实体编译到他们的程序中。一旦函数库实现者改变f,所有用到f的程序都必须重新编译。如果f是non-inline的,用户程序只需重新连接即可。如果函数库采用的是动态连接,那这一升级的f函数可以不知不觉的被程序使用。
4、不要获取inline函数的地址。如果要取得一个inline函数的地址,编译器就必须为此函数产生一个函数实体,无论如何,编译器无法交出一个“不存在函数”的指针。注意,有些编译器可能会使用类的constructors和destructors的函数指针,用以构造和析构一个class对象的数组。另外类的constructors和destructors可能简单,但是其父类的类的constructors和destructors可能是复杂的,所以类的constructors和destructors往往不是inline函数的最佳选择!
5、inline虚函数往往是无效的。虚函数往往是运行时确定的,而inline是在编译时进行的,所以inline虚函数往往无效。当然如果直接用类的对象来使用虚函数,那么对有的编译器而言,也可起到优化的作用。
6、inline函数无法调试。原因请参见上面编译器将函数inline的步骤。所以请在项目后期,对程序进行profile后,再决定将那些函数inline化。
参考文献:
1、《C++语言的设计和演化》2.4 运行时的效率
2、《Effective C++》 条款33
3、《提高C++性能的编程技术》 第8章 内联基础,第9章 内联-站在性能的角度, 第10章 内联技巧
5、《C++ inline 函数》