注意:本文所说的全局变量指的是 variables with static storage,措词来自 c++ 的语言标准文档。html
根据 C++ 标准,全局变量的初始化要在 main 函数执行前完成,常识无疑,可是这个说法有点含糊,main 函数执行前到底具体是何时呢?是编译时仍是运行时?答案是既有编译时,也可能会有运行时(seriously), 从语言的层面来讲,全局变量的初始化能够划分为如下两个阶段(c++11 N3690 3.6.2):ios
static initialization: 静态初始化指的是用常量来对变量进行初始化,主要包括 zero initialization 和 const initialization,静态初始化在程序加载的过程当中完成,对简单类型(内建类型,POD等)来讲,从具体实现上看,zero initialization 的变量会被保存在 bss 段,const initialization 的变量则放在 data 段内,程序加载便可完成初始化,这和 c 语言里的全局变量初始化基本是一致的。c++
dynamic initialization:动态初始化主要是指须要通过函数调用才能完成的初始化,好比说:int a = foo()
,或者是复杂类型(类)的初始化(须要调用构造函数)等。这些变量的初始化会在 main 函数执行前由运行时调用相应的代码从而得以进行(函数内的 static 变量除外)。函数
须要明确的是:静态初始化执行先于动态初始化! 只有当全部静态初始化执行完毕,动态初始化才会执行。显然,这样的设计是很直观的,能静态初始化的变量,它的初始值都是在编译时就能肯定,所以能够直接 hard code 到生成的代码里,而动态初始化须要在运行时执行相应的动做才能进行,所以,静态初始化先于动态初始化是必然的。设计
对于出如今同一个编译单元内的全局变量来讲,它们初始化的顺序与他们声明的顺序是一致的(销毁的顺序则反过来),而对于不一样编译单元间的全局变量,c++ 标准并无明确规定它们之间的初始化(销毁)顺序应该怎样,所以实现上彻底由编译器本身决定,一个比较广泛的认识是:不一样编译单元间的全局变量的初始化顺序是不固定的,哪怕对同一个编译器,同一份代码来讲,任意两次编译的结果都有可能不同[1]。指针
所以,一个很天然的问题就是,若是不一样编译单元间的全局变量相互引用了怎么办?c++11
固然,最好的解决方法是尽量的避免这种状况(防治胜于治疗嘛),由于通常来讲,若是出现了全局变量引用全局变量的窘况,那多半是程序自己的设计出了问题,此时最应该作的是回头从新思考和修改程序的结构与实现,而不是急着穷尽技巧来给错误的设计打补丁。code
---- 说得轻松。htm
好吧,我认可总有那么一些特殊的状况,是须要咱们来处理这种在全局变量的初始化函数里居然引用了别的地方的全局变量的状况,好比说在全局变量的初始化函数里调用了 cout, cerr 等(假设是用来打 log, 注意 cout 是标准库里定义的一个全局变量)[2],那么标准库是怎样保证 cout 在被使用前就被初始化了呢? 有以下几个技巧能够介绍一下。对象
该作法是把对全局变量的引用改成函数调用,而后把全局变量改成函数内的静态变量:
int get_global_x() { static X x; return x.Value(); }
这个方法能够解决全局变量未初始化就被引用的问题,但还有另外一个对称的问题它却无法解决,函数内的静态变量也属于 variables with static storage, 它们析构的顺序在不一样的编译单元间也是不肯定的,所以上面的方法虽然必然能保证 x 的初始化先于其被使用,但却无法妥善处理,若是 x 析构了 get_global_x() 还被调用这种可能发生的状况。
一个改进的作法是把静态变量改成以下的静态指针:
int get_global_x() { static X* x = new X; return x->Value(); }
这个改进能够解决前面提到的 x 析构后被调用的问题,但同时却也引入了另外一个问题: x 永远都不会析构了,内存泄漏还算小问题或者说不算问题,但若是 x 的析构函数还有事情要作,如写文件清理垃圾什么的,此时若是对象不析构,显然程序的正确性都没法保证。
完美一点的解决方案是 Nifty counter, 如今 GCC 采用的就是这个作法[3][7]。假设如今须要被别处引用的全局变量为 x, Nifty counter 的原理是经过头文件引用,在全部须要引用 x 的地方都增长一个 static 全局变量,而后在该 static 变量的构造函数里初始化咱们所须要引用的全局变量 x,在其析构函数里再清理 x,示例以下:
// global.h #ifndef _global_h_ #define _global_h_ extern X x; class initializer { public: initializer() { if (s_counter_++ == 0) init(); } ~initializer() { if (--s_counter_ == 0) clean(); } private: void init(); void clean(); static int s_counter_; }; static initializer s_init_val; #endif
相应的 cpp 文件:
// global.cpp #include "global.h" static X x; int initializer::s_counter_ = 0; void initializer::init() { new(&x) X; } void initializer::clean() { (&x)->~X(); }
代码比较直白,全部须要引用 x 的地方都须要引用 global.h
这个头文件,而一旦引入了该头文件,就必定会引入 initializer 类型的一个静态变量 s_init_val
, 所以虽然不一样编译单元间的初始化顺序不肯定,但他们都确定包含有 s_init_val,所以咱们能够在 s_init_val 的构造函数里加入对 x 的初始化操做,只有在第一个 s_init_val 被构造时才初始化 x 变量,这能够经过 initializer 的静态成员变量来实现,由于 s_counter_ 的初始化是静态初始化,能保证在程序加载后就完成了。
初始化 x 用到了 placement new 的技巧,至于析构,那就是简单粗暴地直接调用析构函数了,这一段代码里的技巧也许有些难看,但都是合法的,固然,同时还有些问题待解决:
首先,由于 x 是复杂类型的变量,它有本身的构造函数,init() 函数初始化 x 以后,程序初始化 x 所在的编译单元时,x 的构造函数还会被再调用一次,同理 x 析构函数也会被调用两次,这显然很容易引发问题,解决的方法是把 x 改成引用:
// global.cpp #include "global.h" // need to ensure memory alignment?? static char g_dummy[sizeof(X)]; static X& x = reinterpret_cast<X&>(g_dummy); int initializer::s_counter_ = 0; void initializer::init() { new(&x) X; } void initializer::clean() { (&x)->~X(); }
其中 static X& x = reinterpret_cast<X&>(g_dummy);
这一行是静态初始化,由于 g_dummy 是编译时就肯定了的(引用是简单类型且以常量为初始值),而 x 只是一个强制转化而来的引用,编译器不会生成调用 x 构造函数和析构函数的代码。经过上面的修改,这个方案已经比较完美了,但遗憾的是它也不是 100% 正确的,这个方案能正确工做的前提是:全部引用 x 的地方都会 include 头文件 global.h,但若是某一个全局变量 y 的初始化函数里没有直接引用 x, 而是间接调用了另外一个函数 foo,再经过 foo 引用了 x,此时就可能出错了,由于 y 所在的编译单元里可能并无直接引用 x,所以颇有可能就没有 include 头文件 global.h,那么 y 的初始化就颇有可能发生在 x 以前。。。
这个问题在 gcc c++ 的标准库里也没有获得解决,有兴趣的能够看看这个讨论。
[参考]
[1] http://isocpp.org/wiki/faq/ctors#static-init-order
[2] https://gcc.gnu.org/onlinedocs/libstdc++/manual/io.html#std.io.objects
[3] https://gcc.gnu.org/onlinedocs/libstdc++/libstdc++-html-USERS-3.4/ios__init_8cc-source.html
[4] https://social.msdn.microsoft.com/Forums/vstudio/en-US/637a4c27-3e30-4b88-b36d-b5b720cf0d04/why-are-cout-cin-initialized-once-and-only-once-given-the-scheme-below-in-the-iostream?forum=vclanguage
[5] http://www.petebecker.com/js/js199905.html
[6] http://blogs.msdn.com/b/ce_base/archive/2008/06/02/dynamic-initialization-of-variables.aspx
[7] http://cs.brown.edu/people/jwicks/libstdc++/html_user/globals__io_8cc-source.html