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

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

 


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

 


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

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

t.h


[cpp]
#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 

#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[cpp]
#include "t.h"  
 
B B::_b; 
 
A *g_a = NULL; 
 
A::A() 

    B::_b.setA(this); 
    g_a = this; 

#include "t.h"

B B::_b;

A *g_a = NULL;

A::A()
{
 B::_b.setA(this);
 g_a = this;
}
t.cpp


[cpp]
#include "t.h"  
 
A a; 
 
int main() 

    printf("a=%p, b.a=%p, g_a=%p\n", &a, B::_b.a(), g_a); 

#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。

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


[plain]
g++ -o t tt.cpp t.cpp 

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


[plain]
a=0x804a024, b.a=0x804a024, g_a=0x804a024 

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

 


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


[plain]
g++ -o t t.cpp tt.cpp 

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


[plain]
a=0x804a01c, b.a=(nil), g_a=0x804a01c 

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变量,都可以得到正确的值。