web-dev-qa-db-ja.com

静的変数の初期化順序

C++は、コンパイル単位(.cppファイル)の変数が宣言順に初期化されることを保証します。コンパイル単位の数について、このルールはそれぞれ個別に機能します(クラス外の静的変数を意味します)。

ただし、変数の初期化の順序は、異なるコンパイル単位間で未定義です。

GccとMSVCのこの順序に関する説明はどこにありますか(これに依存することは非常に悪い考えであることを知っています-新しいGCCメジャーおよび異なるOSに移行するときにレガシーコードで発生する可能性のある問題を理解するだけです) ?

58
Dmitry Khalatov

あなたが言うように、順序は異なるコンパイル単位間で未定義です。

同じコンパイル単位内で、順序は明確に定義されています。定義と同じ順序です。

これは、これが言語レベルではなく、リンカーレベルで解決されるためです。したがって、リンカのドキュメントを実際に確認する必要があります。私はこれがどんな役に立つ方法でも役立つと本当に疑いますが。

Gccの場合:チェックアウト ld

リンクされているオブジェクトファイルの順序を変更しても、初期化の順序が変更されることがあります。そのため、リンカだけでなく、ビルドシステムによってリンカがどのように呼び出されるかを心配する必要があります。問題を解決しようとしても、実際には初心者ではありません。

これは通常、独自の初期化中に相互に参照するグローバルを初期化する場合にのみ問題になります(したがって、コンストラクターを持つオブジェクトにのみ影響します)。

問題を回避する方法があります。


  • 注1:グローバル:
    main()の前に潜在的に初期化される静的ストレージ期間変数を参照するために大まかに使用します。
  • 注2:潜在的に
    一般的な場合、静的ストレージ期間変数はmainの前に初期化されることが予想されますが、コンパイラは状況によっては初期化を延期できます(ルールは複雑です。詳細については標準を参照してください)。
69
Martin York

モジュール間のコンストラクターの順序は、主にオブジェクトをリンカーに渡す順序の関数であると思われます。

ただし、GCCでは se init_priority順序を明示的に指定するには グローバルアクターの場合:

class Thingy
{
public:
    Thingy(char*p) {printf(p);}
};

Thingy a("A");
Thingy b("B");
Thingy c("C");

予想どおり「ABC」を出力しますが、

Thingy a __attribute__((init_priority(300))) ("A");
Thingy b __attribute__((init_priority(200))) ("B");
Thingy c __attribute__((init_priority(400))) ("C");

「BAC」を出力します。

17
Mike F

絶対に必要な場合を除き、この情報に頼るべきではないことは既にわかっているので、ここで説明します。さまざまなツールチェーン(MSVC、gcc/ld、clang/llvmなど)での私の一般的な観察では、オブジェクトファイルがリンカーに渡される順序は、初期化される順序です。

これには例外があり、私はそれらのすべてを主張していませんが、ここに私が自分自身に出くわしたものがあります:

1)4.7より前のGCCバージョンは、実際にはリンク行の逆の順序で初期化されます。 GCCのこのチケット は変更が行われたときであり、初期化の順序に依存する多くのプログラムを壊しました(私のものを含む!)。

2)GCCおよびClangでは、 コンストラクター関数の優先順位 を使用すると、初期化の順序を変更できます。これは、「コンストラクター」であると宣言された関数にのみ適用されることに注意してください(つまり、グローバルオブジェクトコンストラクターと同じように実行する必要があります)。私はこのような優先順位を使用してみましたが、コンストラクター関数の優先順位が最も高い場合でも、優先順位のないすべてのコンストラクター(例:通常のグローバルオブジェクト、優先順位のないコンストラクター関数)は初期化されますfirst。言い換えれば、優先順位は優先順位を持つ他の機能にのみ関連していますが、実際のファーストクラスの市民は優先順位のない人々です。さらに悪いことに、このルールは、上記のポイント(1)により、4.7より前のGCCでは事実上反対です。

3)Windowsには、 DllMain() と呼ばれる非常に便利で便利な共有ライブラリ(DLL)エントリポイント関数があり、定義されている場合、DLL_PROCESS_ATTACHに等しいパラメータ "fdwReason"で実行されますすべてのグローバルデータが初期化されており、beforeを消費するアプリケーションがDLLの関数を呼び出す機会を持っています。これは非常に便利な場合があり、GCCまたはClangを使用するClangまたはC++を使用する他のプラットフォームでは、これとまったく同じnotの動作はありません。最も近いのは、コンストラクター関数を優先的に作成することです(上記のポイント(2)を参照)。これはまったく同じではなく、DllMain()が機能する多くのユースケースでは機能しません。

4)CMakeを使用してビルドシステムを生成している場合(多くの場合これを行います)、入力ソースファイルの順序は、リンカーに与えられた結果オブジェクトファイルの順序になることがわかりました。ただし、多くの場合、アプリケーション/ DLLは他のライブラリでもリンクしています。その場合、それらのライブラリは入力ソースファイルのリンク行afterにあります。グローバルオブジェクトの1つを初期化する非常に最初のものにしたい場合は、幸運であり、そのオブジェクトを含むソースファイルを最初に置くことができますソースファイルのリスト。ただし、初期化する最後の1つにすることを探している場合(これはDllMain()の動作を効果的に複製できます!)、その1つでadd_library()を呼び出すことができますソースファイルを作成して静的ライブラリを作成し、結果の静的ライブラリをアプリケーション/ DLLのtarget_link_libraries()呼び出しの最後のリンク依存関係として追加します。この場合、グローバルオブジェクトが最適化される可能性があることに注意してください。 -whole-archive フラグを使用して、その特定の小さなアーカイブファイルの未使用シンボルを削除しないようにリンカーを強制できます。

クロージングチップ

リンクされたアプリケーション/共有ライブラリの結果の初期化順序を完全に知るには、-print-mapをldリンカーに渡し、.init_arrayのgrep(または4.7より前のGCCでは、.ctorsのgrep)を渡します。すべてのグローバルコンストラクターは、初期化される順序で出力され、4.7より前のGCCでは順序が逆になることに注意してください(上記のポイント(1)を参照)。

この答えを書く動機は、この情報を知る必要があり、初期化順序に依存する以外に選択肢がなく、他のSO投稿とインターネットフォーラムの大部分は多くの実験を通じて学習されたもので、これにより一部の人々がそれを行う時間を節約できることを願っています。

13
Nicholas Smith

http://www.parashift.com/c++-faq-lite/ctors.html#faq-10.12 -このリンクは移動します。この one はより安定していますが、それを探し回る必要があります。

編集:osgxはより良い link を提供しました。

4
Ray Tayek

CのバックグラウンドからのMartinのコメントに加えて、私は常に静的変数をプログラム実行可能ファイルの一部として考え、データセグメントにスペースを組み込み、割り当てました。したがって、静的変数は、コードが実行される前に、プログラムのロード時に初期化されると考えることができます。これが発生する正確な順序は、リンカによって出力されるマップファイルのデータセグメントを調べることで確認できますが、ほとんどの意図と目的では、初期化は同時に行われます。

編集:静的オブジェクトの構築順序に応じて、移植性がなくなる傾向があり、おそらく回避する必要があります。

1
SmacL

最終順序を本当に知りたい場合は、コンストラクタが現在のタイムスタンプを記録するクラスを作成し、各cppファイルにクラスのいくつかの静的インスタンスを作成して、初期化の最終順序を知ることをお勧めします。各ファイルに同じタイムスタンプが付けられないように、コンストラクタに少し時間のかかる操作を入れてください。

0
Darien Pardinas