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