现在的位置: 首页 > 综合 > 正文

C++知识总结(一)—— 全局变量的初始化顺序

2014年09月02日 ⁄ 综合 ⁄ 共 2587字 ⁄ 字号 评论关闭

c++标准没有规定不同编译单元(cpp文件)中的全局对象的构造顺序,只规定了同一编译单元中的全局对象按照其定义的顺序构造并倒序析构。

c++之所以性能相对较好,是因为很多东西c++标准并没有规定死,这样编译器就可以进行各种优化。但这样也导致c++的使用门槛相比其他语言要高一些。

例如,某个项目的不同cpp文件中定义了多个全局对象,其中一个是全局日志对象(gLogger)。假设其他几个全局对象的构造和析构函数中使用了gLogger输出一些调试信息。在程序开始运行时,所有的全局对象会确保在入口函数(main)之前构造,假设gLogger最后构造。那在其他几个全局对象的构造函数中使用gLogger时就会出错。在程序退出时,假设gLogger最先析构,那在其他几个全局对象的析构函数中使用gLogger时也会出错(称为dead-reference)。

首先是一条清规戒律:最好不要编写依赖于编译顺序的程序。总会有人打破戒律,假设确实有这个需求,并且用的是VC++编译器(其他的编译器我不会),那有一种方法可以控制全局对象的构造顺序。看一段代码:

#include <iostream>
using namespace std;

class CLogger 
{
public:
	CLogger() { cout << "CLogger()" << endl; }
	~CLogger() { cout << "~CLogger()" << endl; }
};

CLogger gLogger;

int main()
{
	return 0;
}

这段代码肯定可以正常工作,cout也是一个ostream类型的全局对象,它怎么可以保证会先于gLogger之前被构造呢?实际上这是通过预处理器指令(preprocessor directive)来控制的。

#pragma init_seg(compiler)
#pragma init_seg(lib)
#pragma init_seg(user)
#pragma init_seg("user_defined_segment_name")

引用两段E文:

The purpose ofthis directive is to give the developer the ability to group the constructorsin an application. This would be useful if some objects relied upon theexistence of other objects to function correctly. Objects that are groupedtogether
using #pragma init_seg(compiler) are constructed before all otherobjects and destroyed after all other objects in the application. This is usedfor objects in the run-time libraries. For example, because cin and cout may ormay not be constructed yet, using
these objects in your constructor ordestructor that uses the init_seg(compiler) #pragma would be unwise.

Objects thatare grouped together using #pragma init_seg(lib) are constructed after anddestructed before objects that are in modules compiled with #pragmainit_seg(compiler), but before all other objects in the application. Objectsthat are grouped
together using #pragma init_seg(user) are constructed afterand destructed before objects that are in modules compiled with #pragmainit_seg(compiler) and #pragma init_seg(lib). In other words, objects that aregrouped together using #pragma init_seg(user) are
constructed and destructed atthe same time as all other static objects that were not grouped using #pragmainit_seg.

主要表达了这么几个意思:

* 预处理器指令可以给构造函数分组,让某些先执行,某些后执行。这样就可以控制全局对象的构造(析构)顺序。

* #pragma init_seg(compiler)分组内的对象最先构造,最后析构。

* #pragma init_seg(lib)分组内的对象在(compiler)之后构造,在其之前析构。

* #pragma init_seg(user)分组内的对象和普通的全局对象一样,在(compiler)(lib)之后构造,在其之前析构。

* 我们不应该用#pragma init_seg(compiler),它应该只给run-time libraries用,否则是cout先构造还是gLogger先构造又不确定了,这问题就麻烦了。

* #pragmainit_seg("user_defined_segment_name"),先无视它,除非要修改startup code

最后的结论就是我们可以使用#pragma init_seg(lib)指令,来确保某些全局对象先于其他普通的全局对象构造(后于他们析构)

#pragma warning(disable: 4074)
#pragma init_seg(lib)
static CLogger gLogger;
…

NOTE: Only one init_seg directive can appear in asingle source file. Otherwise, the compiler generates "error C2356:initialization segment must not change during translation unit."

抱歉!评论已关闭.