C++ 全局构造函数调用的顺序

来源:互联网 发布:网络骗局麻将 编辑:程序博客网 时间:2024/06/01 10:09

C++的全局类和静态类的构造函数是在main函数之前调用的。但是,不同的类的构造函数以什么顺序调用呢?


对于g++编译器来说,这个顺序是由链接时,文件顺序决定的。


我们用一个例子来说明这一点。

我们有3个文件:t1.h, t1.cpp和tt1.cpp,内容分别是

t.h

#ifndef T_H#define T_H#include <stdio.h>class A {public:   A();};class B {public:   B(){ a_ = NULL; }   void setA(A* a) { a_ = a; }   A * a() { return a_; }   static B _b;private:   A *a_;};extern A *g_a;#endif

tt.cpp
#include "t.h"B B::_b;A *g_a = NULL;A::A(){B::_b.setA(this);g_a = this;}
t.cpp

#include "t.h"A a;int main(){printf("a=%p, b.a=%p, g_a=%p\n", &a, B::_b.a(), g_a);}
t.h定义了类A和B,其中,在A的构造中,A将自己的指针付给B::_b.a_和g_a。

那么,如果以这样的顺序编译

g++ -o t tt.cpp t.cpp
执行 ./t,得到的结果是

a=0x804a024, b.a=0x804a024, g_a=0x804a024
这正是我们期望的结果。


如果,按照这样的顺序编译

g++ -o t t.cpp tt.cpp
得到的结果是

a=0x804a01c, b.a=(nil), g_a=0x804a01c

那么,为什么先编t.cpp,在编tt.cpp,会得到b.a的结果为null呢?


 这应该和ELF文件的格式有关系。

在C/C++语言中,全局变量、静态变量将被放在global数据段,当elf文件被加载到系统中时,global段的数据直接被映射到内存中。

但是,对于C++来说,全局和静态类对象,还必须调用构造函数,这些构造函数的调用,就被放在了init段。这个段是一个代码段,在elf被载入时被执行。

那么,很自然,g++按照链接的顺序,依次把全局类对象的构造放在了init段中。

于是,上面由于t.cpp先被链接,tt.cpp后被链接,因此,a的构造函数就先于B::b_的构造函数调用。 这样,当A::A()被调用时,B::b_::a_的值就被设置为a的指针。

当B::B()被调用时,a_的值被初始化为NULL。

于是,最终的输出结果,b.a=(nil)。


这说明,在C++内部,在全局构造函数中,访问其他全局或者静态变量,其结果是不可预知的。

要解决这个问题,我们使用指针变量。例如,例子中的g_a。

指针变量是一个变量而不是类对象,因此当elf文件被映射到内存中时,g_a的变量值就已经确定,无需额外的代码执行。因此,这可以保证在任意时刻访问g_a变量,都可以得到正确的值。




原创粉丝点击