ソースファイルが複数あって、一方のグローバル変数を他方のグローバル変数より確実に先に初期化したいと考えました。先に初期化したい方のソースファイルにある変数は全て先に初期化するものとします。
まず、これは標準 C++ の範囲では実現できません。同一コンパイル単位(≒同一ソースファイル)ではおおむね書いた順に初期化されますが、異なるコンパイル単位では順序は制御できません。
リンカの挙動に依存していいのであれば、リンカにオブジェクトファイルを渡す順序を工夫することで、コンパイル単位間での初期化順序を制御することができる場合があります。少なくとも Visual C++ + Windows や gcc + ld + Linux で制御できることは確認しましたが、インクリメンタルリンクを行うと順序が乱れることも同時に確認したため、あまり信頼性はありません。
というわけで、コンパイラの独自拡張を使って制御する方法を考えます。
Visual C++ + Windows の場合、 #pragma init_seg
を使えます。優先度制御で使えるのは3段階しかなく、そのうち1つ(compiler
)は予約されており事実上2段階(user
= 通常 と lib
= 優先)しかないので、若干使いづらいのが難点です。
#pragma init_seg(lib) std::vector<int> vec = { 1, 2 }; // other よりも先に初期化される
std::vector<int> other = vec;
gcc/clang の場合、 __attribute__((init_priority(n)))
を使えます。 n はかなり自由な範囲(101~65535)が指定できます。65535 を指定すると優先度最低になり attribute を付けない時と同じ扱いになるようです。(が、この点は明記されてないんですよね)
__attribute__((init_priority(65534))) std::vector<int> vec = { 1, 2 }; // other よりも先に初期化される
std::vector<int> other = vec;
ちなみに、優先度を通常より上げた初期化ルーチン内でライブラリ関数を呼び出すと、場合によってはライブラリ側の初期化が行われる前に呼び出してしまうことがあるため、気を付ける必要があります。(たとえば printf デバッグしようとしたら SEGV したので write(2) を使った)
さて、ここまでは良いんですが、この init_priority
、 OSX では効きません。より正確には、同一コンパイル単位内では指定したとおりに優先度が制御されますが、コンパイル単位間では制御されません(リンカに渡した順序に依存した初期化順序になる)。今回私は OSX でも制御したいので、この方法は使えないことになります。
どうしようかなぁ。
その2に続く。
グローバル変数の初期化順序を制御する(その2)
その1の続き。今度は代替方法。 「最初に初期化したい」が実際には「いつ使うか分からないからそれまでに初期化したい」であるのなら、最初に使う時に初期化すればよいことになり…