C++关键字 static、extern、inline 单独的含义和用法都不难,但组合起来的含义常常比较含糊,本文主要分析 static inline 和 extern inline。引用 Linux 之父 linus 的原话:
"static inline" means "we have to have this function, if you use it, but don't inline it, then make a static version of it in this compilation unit". "extern inline" means "I actually have an extern for this function, but if you want to inline it, here's the inline version".
,这句话深入浅出的区别了 static inline 与 extern inline 两个关键字组合的区别,如何理解这句话,我们后面再来分析。
1、static、extern与inline含义
static: 修饰的变量或函数具有内链接属性,不可被其他文件引用,好处即外部文件中函数或变量可以重名,static的变量存储在GVAR(global value)内存区(静态存储区 ".data"),所以如果static修饰函数内局部变量,即使函数执行完成,堆栈释放而static变量不会被释放,但需要注意的是即使变量在全局区,但是可见域并没有改变,仅函数内可见。
// comm.h...static int age = 666;// a.cpp#include "comm.h"void aprocess() { age = -666;}// b.cpp#include "comm.h"void bprocess() { age = 2333;}
extern:修饰的变量或内存具有外链接属性,外部文件通过声明就可以引用其他文件中定义的符号(函数或变量),一般情况下函数或全局变量默认为extern属性。 inline:inline和链接属性没有直接的关系,且只用来修饰函数,inline修饰的函数是对编译器做调用点代码展开建议,但具体是否展开由编译器决定,如果函数复杂度较高inline可能不会生效。因为内联函数要在调用点展开,所以编译器必须所有调用点可见函数的定义,不然就成为了普通函数调用,所以将内联函数的定义放在头文件里实现是合适的,不然你需要为每个文件实现一次。如果你真的打算每个文件里都实现一次,那么最好保证每个定义都是一样的,否则行为是未定义的。 Q: 既然有可能 inline 函数产生与普通函数相似的调用效果,那么会在符号表产生符号吗?在头文件定义会出现重定义吗? A: 会产生符号,函数均会产生符号,但是inline函数由于其特殊性,默认是内链接属性的符号, 所以不会出现重定义问题。
PS:const修饰变量属性也是内链接的,也即在头文件定义不会造成重定义,但可以通过extern 双重修饰,使变量具有外链接属性,此时定义不能放在头文件(避免重定义),const 也可以用来修饰类的成员函数,此时类不能修改对象的内容。
2、inline的特点
优点:
- inline 修饰的函数,函数代码被放入符号表中,使用时进行替换(像宏一样展开),效率很高;
- 内联函数也是函数,在调用一个内联函数时,首先会检查参数问题,保证调用正确,像对待真正函数一样,消除了隐患及局限性;
- inline 可以作为类的成员函数,同样可以使用类的保护成员及私有成员;
缺点:
- 如果函数的代码较长,内联将消耗过多存储空间;
- 如果函数体内有循环,那么执行函数代码时间比调用开销更大。
inline和宏的区别
宏是在代码处不加任何验证的简单替代,而内联函数是将代码直接插入调用处,而减少了普通函数调用时的资源消耗。- 宏不是函数,只是在编译前(编译预处理阶段)将程序中有关字符串替换成宏体;
- inline函数是函数,但在编译中不单独产生代码,而是将有关代码嵌入到调用处;
3、总结
再理解开篇那段话,static inline首先我们必须认识到,这是一个static函数,也即函数是文件内链接的,定义放在头文件,即使多处包含也不会出现重定义问题,如果你将它放进源文件反而可能会出错,因为这时其他文件无法访问,所以必须定义在头文件,然后在调用点做内联展开。而extern inline首先函数是extern的,也即函数可能是从其他文件引入的(所以不可能定义在头文件),如果本文件看不到定义,则只会做普通函数调用,如果能看到定义,则可以尝试inline展开。