在使用C++时,编译项目时偶尔会发生链接,这些错误很多时候是偶发的,可能再编一次,或者换个模式,换个参数,就没有编译错误了,但之后可能还会出现。由于是在编译阶段,问题不容易排查。这样的错误有可能就是静态变量的初始化问题。
- 简单概括:当静态变量的初始化过程中,需要其他静态变量的值时,就有可能产生不确定的链接错误。
- 主要原因:C++不固定静态变量的初始化顺序,在初始化静态变量时,如果你需要其他静态变量的值,但这个值又没有初始化,问题就发生了。
- 解决方案:
- 尽量不要在静态初始化时引入其他静态变量,尤其避免循环依赖。
- 将静态变量,放到函数里面去,即:
每次想访问这个静态变量时,改为用A& variable_name(){ static A variable = ...; return variable; }
variable_name()
来进行访问,这个函数也没必要是成员函数,跟这个类一起放到头文件里就可以了(内联inline)。在函数内的局部静态变量第一次访问是初始化,之后自动再调用函数自动略过初始化语句。- 为C++入口的_init()函数提供自定义实现,这个函数负责初始化静态变量,在这个函数内部人工确定顺序。(一般非必要不使用)
好的,接下来我们深入剖析下这个问题:
- C语言没有这样的链接错误:C语言可以很简单的处理初始化变量,无论是基本类型还是结构体,在编译阶段就可以将初始化值放入.data节,自然就不存在这样的问题。C++的对象是由构造函数来构建的,编译时不执行代码,因此无法确定具体值,只能放在运行时完成。
- 静态变量的初始化如果不牵涉其他任何自定义类型则不会出现问题:因为不会出现顺序问题,所以可以按正常语法初始化。
- 那些需要其他静态变量的值做初始化的静态变量,最好按照方案2重写避免链接问题。
顺便说一句,对于非静态变量,比如类中的成员变量,在构造函数中,尽量以初始化列表的方式初始化,即
A(int _a, double _b, B & _c)
: a(_a),b(_b),c(_c),d()
{}
如果在函数体内执行,实际上是用默认构造函数初始化了一次,又赋值了一次。
而且,如果是常量对象和引用对象,则必须如此初始化!!
- 还有一个值得注意的点:类内成员的初始化顺序,是按照他们在头文件中的排列顺序决定的,和构造函数的初始化列表无关。
参考资料:
- 《Effective C++》第三版,条款04
- 《高级C/C++编译技术》:第6章