一、内联引出的问题
在将一个内联变量定义到编译单元时,然后再按正常的方式使用时,编译会报一个错误“odr-used”。ODR,One Definition Rule,单一定义规则。在C/C++程序中,变量的定义只能有一处,至于ODR的规则如何定义,这个在C++的标准文档中说明的非常清楚。大家可以去查询,有时间再总结一下一起分析。
这个问题就回到了inline的问题相关,即inline(and odr-used)变量和函数,在每个编译单元是可以存在一个定义的。这也是为什么inline的变量可以在不同的编译单元使用的原因。
二、内联的优势
在前面分析的基础上,可以进一步提出,内联对编译是一种类似于提示的机制,告诉编译器,你可以看到更多的代码和更大的范围,至于能不能优化,就看编译器的能力了。
编译器在得到更多的信息后,可以有以下几个方面的优化:
1、IPO(Interprocedural Optimization)
过程优化,这个其实非常好理解,比如for循环内的变量,在低版本的编译器中可能是一个需要手动优化的目标,但在高版本编译器会自动根据情况优化到手动指定的效果。同样对于一些运算中间过程可以进行省略而直接使用结果(常量折叠)。这就需要编译器看到的代码要相对完全,包括本文提到的inline。
2、LTO(Link Time Optimization)
链接时优化,它其实是IPO的一部分。即在链接时对相关的代码进行优化。也就是说,编译器会根据不同的编译单元编译出不同的目标文件,这些目标文件就有可能进行链接时的优化。
可能有人因此会说,既然编译器如此高大威猛,为何还需要程序员小白们费心的搞这个inline?其实非常好理解,再威猛的战士也有疏漏的时候儿,不可能面面俱到,而开发者就是给编译器一个指标或者说建议,让其知晓,此处可能可以优化。另外一个就是解决文章一开头提到的ODR的问题。
三、说明
其实在前面就提到过对虚拟函数的内联优化支持,可以肯定的说,普遍意义上讲肯定是内联无法适配虚函数。但是在一些特殊的情况下,比如下面的代码:
class A{
public:
inline virtual void Test(){...}
};
class B:public A
{
public:
inline virtual void Test(){...}
};
inline void Get(A& a){
a.Test();
}
int main(){
A a;
B b;
b.Test();//可以内联
//下面不确定
Get(b);
Get(a);
return 0;
}
其实这种就是典型的编译完全可以明白并确定虚拟函数inline Test函数归属的具体的对象,所以,就可以进行内联的优化处理,其它的情况也是类似。而Get函数中由于引用可能产生动态绑定,所以就无法进行准确的inline。编译器对代码的优化随着技术的进步在不断的推进,比如现在的AI大模型的出现,以后会不会出现完全由编译器为主,而人为设置标记为辅的编程方式,也不好说。毕竟,AI自己编程都出现了,自已编程自己编译,自己测试并部署也不是不可能,这是不是《终结者》里的天网的雏形?
四、总结
许多的技术看起来很简单,用起来也没有什么可以体察到困难的地方。比如这个inline,反正写不写以后编译器还要看自己心情来处理。但其实真正的向内部观察其原理,会发现有很多技术点在等着你去融会贯通。可以把inline的函数和变量通过汇编去深入分析一下,再查看一下它的具体的实现和优化的过程,好多细节的明白,会是开发者提升自己能力的一个重要的契机!