web-dev-qa-db-ja.com

C ++静的初期化順序の問題を見つける

静的初期化順序fiasco でいくつかの問題に遭遇しました。そして、発生する可能性のあるものを見つけるために多くのコードをくまなく調べる方法を探しています。これを効率的に行う方法に関する提案はありますか?

編集:静的な初期化順序の問題を解決する方法についていくつかの良い答えを得ていますが、それは本当に私の質問ではありません。この問題の影響を受けるオブジェクトを見つける方法を知りたい。この点に関して、エヴァンの答えはこれまでのところ最高のようです。 valgrindを使用できるとは思いませんが、同様の機能を実行できるメモリ分析ツールがあるかもしれません。これは、特定のビルドの初期化順序が間違っている場合にのみ問題をキャッチし、ビルドごとに順序が変わる可能性があります。おそらく、これをキャッチする静的分析ツールがあります。プラットフォームは、AIX上で実行されるIBM XLC/C++コンパイラーです。

57
Fred Larson

初期化の解決順序:

まず、これは一時的な回避策です。削除しようとしているグローバル変数がありますが、まだ時間がありません(最終的には削除しますか?:-)

_class A
{
    public:
        // Get the global instance abc
        static A& getInstance_abc()  // return a reference
        {
            static A instance_abc;
            return instance_abc;
        }
};
_

これにより、最初の使用時に初期化され、アプリケーションの終了時に破棄されることが保証されます。

マルチスレッドの問題:

C++ 11doesは、これがスレッドセーフであることを保証します。

§6.7[stmt.dcl] p4
変数の初期化中に制御が同時に宣言に入る場合、同時実行は初期化の完了を待機します。

ただし、C++ 03は、静的関数オブジェクトの構築がスレッドセーフであることを公式に保証しませんnot。したがって、技術的にはgetInstance_XXX()メソッドはクリティカルセクションで保護する必要があります。明るい面で、gccにはコンパイラの一部として明示的なパッチがあり、各静的関数オブジェクトはスレッドが存在する場合でも1回だけ初期化されることが保証されています。

注意:ロックのコストを回避するために、 二重チェックロックパターン を使用しないでください。これはC++ 03では機能しません。

作成の問題:

作成時には、使用する前に作成されることが保証されているため、問題はありません。

破壊の問題:

オブジェクトが破棄された後にアクセスする潜在的な問題があります。これは、別のグローバル変数のデストラクタからオブジェクトにアクセスした場合にのみ発生します(グローバルとは、ローカルではない静的変数を指します)。

解決策は、破壊の順序を強制することを確認することです。
破壊の順序は、構築の順序とまったく逆であることに注意してください。したがって、デストラクタでオブジェクトにアクセスする場合、オブジェクトが破壊されていないことを保証する必要があります。これを行うには、呼び出しオブジェクトが構築される前にオブジェクトが完全に構築されることを保証する必要があります。

_class B
{
    public:
        static B& getInstance_Bglob;
        {
            static B instance_Bglob;
            return instance_Bglob;;
        }

        ~B()
        {
             A::getInstance_abc().doSomthing();
             // The object abc is accessed from the destructor.
             // Potential problem.
             // You must guarantee that abc is destroyed after this object.
             // To guarantee this you must make sure it is constructed first.
             // To do this just access the object from the constructor.
        }

        B()
        {
            A::getInstance_abc();
            // abc is now fully constructed.
            // This means it was constructed before this object.
            // This means it will be destroyed after this object.
            // This means it is safe to use from the destructor.
        }
};
_
66
Martin York

この問題を追跡するためのコードを少し書きました。 Windows/VC++ 2005では正常に動作していたが、Solaris/gccでの起動時にクラッシュする適切なサイズのコードベース(1000以上のファイル)があります。次の.hファイルを作成しました。

#ifndef FIASCO_H
#define FIASCO_H

/////////////////////////////////////////////////////////////////////////////////////////////////////
// [WS 2010-07-30] Detect the infamous "Static initialization order fiasco"
// email warrenstevens --> [initials]@[firstnamelastname].com 
// read --> http://www.parashift.com/c++-faq-lite/ctors.html#faq-10.12 if you haven't suffered
// To enable this feature --> define E-N-A-B-L-E-_-F-I-A-S-C-O-_-F-I-N-D-E-R, rebuild, and run
#define ENABLE_FIASCO_Finder
/////////////////////////////////////////////////////////////////////////////////////////////////////

#ifdef ENABLE_FIASCO_Finder

#include <iostream>
#include <fstream>

inline bool WriteFiasco(const std::string& fileName)
{
    static int counter = 0;
    ++counter;

    std::ofstream file;
    file.open("FiascoFinder.txt", std::ios::out | std::ios::app);
    file << "Starting to initialize file - number: [" << counter << "] filename: [" << fileName.c_str() << "]" << std::endl;
    file.flush();
    file.close();
    return true;
}

// [WS 2010-07-30] If you get a name collision on the following line, your usage is likely incorrect
#define FIASCO_Finder static const bool g_psuedoUniqueName = WriteFiasco(__FILE__);

#else // ENABLE_FIASCO_Finder
// do nothing
#define FIASCO_Finder

#endif // ENABLE_FIASCO_Finder

#endif //FIASCO_H

そして、ソリューションのevery .cppファイル内に、これを追加しました:

#include "PreCompiledHeader.h" // (which #include's the above file)
FIASCO_Finder
#include "RegularIncludeOne.h"
#include "RegularIncludeTwo.h"

アプリケーションを実行すると、次のような出力ファイルが得られます。

Starting to initialize file - number: [1] filename: [p:\\OneFile.cpp]
Starting to initialize file - number: [2] filename: [p:\\SecondFile.cpp]
Starting to initialize file - number: [3] filename: [p:\\ThirdFile.cpp]

クラッシュが発生した場合、犯人はリストされている最後の.cppファイルにあるはずです。そして、少なくとも、このコードは実行するコードのabsolute firstである必要があるため、ブレークポイントを設定するのに適した場所を提供します(その後、コードをステップスルーして、初期化されているグローバル)。

注:

  • 「FIASCO_Finder」マクロをできるだけファイルの上部に近づけることが重要です。他の#includesの下に配置すると、ファイルを特定する前にクラッシュする危険があります。

  • Visual Studioとプリコンパイル済みヘッダーを使用している場合、この追加のマクロ行を.cppファイルのallに追加すると、検索と置換ダイアログを使用して既存の#を置き換えることができます。同じテキストにFIASCO_Finder行を加えた「precompiledheader.h」を含めます(「正規表現」をオフにすると、「\ n」を使用して複数行の置換テキストを挿入できます)

31
Warren Stevens

コンパイラーに応じて、コンストラクターの初期化コードにブレークポイントを配置できます。 Visual C++では、これは_initterm関数。呼び出す関数のリストの開始ポインターと終了ポインターが与えられます。

次に、各関数にステップインして、ファイルと関数名を取得します(デバッグ情報を有効にしてコンパイルしたと仮定します)。名前を取得したら、関数から抜け出します(_initterm)そして_initterm終了します。

それはあなたに与えますallあなたのコード内のものだけでなく、静的初期化子-それは完全なリストを取得する最も簡単な方法です。制御できないもの(サードパーティのライブラリにあるものなど)を除外できます。

この理論は他のコンパイラにも当てはまりますが、関数の名前とデバッガの機能は変わる可能性があります。

14
paxdiablo

コンパイラによって生成されるC++を本質的に「初期化」するコードがあります。このコード/その時点での呼び出しスタックを見つける簡単な方法は、コンストラクターでNULLを間接参照する静的オブジェクトを作成することです。デバッガーで中断して少し調べてください。 MSVCコンパイラーは、静的初期化のために繰り返される関数ポインターのテーブルをセットアップします。このテーブルにアクセスし、プログラムで発生するすべての静的初期化を決定できるはずです。

5
Ben Murrell

valgrindを使用して、初期化されていないメモリの使用状況を見つけることができます。 「静的初期化順序の失敗」に対する最も良い解決策は、次のようなオブジェクトのインスタンスを返す静的関数を使用することです。

class A {
public:
    static X &getStatic() { static X my_static; return my_static; }
};

静的オブジェクトにアクセスするには、getStaticを呼び出します。これにより、最初の使用時に初期化されることが保証されます。

初期化解除の順序を気にする必要がある場合は、静的に割り当てられたオブジェクトの代わりに、新しいオブジェクトを返します。

編集:冗長な静的オブジェクトを削除しました、なぜかわかりませんが、元の例で静的を一緒に持つ2つの方法を組み合わせました。

5
Evan Teran

私たちはいくつかの問題に遭遇しました静的初期化順序の失敗、とくしの方法を探しています検索するコード全体発生の可能性。任意の提案これを効率的に行う方法について

これは些細な問題ではありませんが、少なくとも、解析しやすい中間形式のコード表現があれば、かなり簡単な手順に従って実行できます。

1)自明でないコンストラクターを持つすべてのグローバルを検索し、リストに入れます。

2)これらの非自明に構築されたオブジェクトのそれぞれについて、コンストラクターによって呼び出される潜在的な関数ツリー全体を生成します。

3)非自明なコンストラクター関数ツリーをウォークスルーし、コードが他の自明ではないグローバル(ステップ1で生成したリストに非常に便利です)を参照している場合、潜在的な初期静的初期化順序があります問題。

4)ステップ1で生成されたリストを使い果たすまで、ステップ2と3を繰り返します。

注:単一クラスのグローバルが複数ある場合、グローバルインスタンスごとに1回ではなく、オブジェクトクラスごとに1回、潜在的な関数ツリーにアクセスするだけで、これを最適化できる場合があります。

4
Adisak

すべてのグローバルオブジェクトを、関数内で静的に宣言されたオブジェクトへの参照を返すグローバル関数に置き換えます。これはスレッドセーフではないため、アプリがマルチスレッドの場合、pthread_onceやグローバルロックなどのトリックが必要になる場合があります。これにより、使用前にすべてが確実に初期化されます。

さて、あなたのプログラムは動作する(hurrah!)か、循環依存関係があるために無限ループに陥る(再設計が必要)か、次のバグに進む。

2
Steve Jessop

最初に行う必要があるのは、重要なコンストラクタを持つすべての静的オブジェクトのリストを作成することです。

そのため、一度に1つずつプラグインするか、単純にすべてをシングルトンパターンオブジェクトに置き換える必要があります。

シングルトンパターンは多くの批判を受けますが、怠zyな「必要に応じて」構成は、現在および将来の問題の大部分を解決するためのかなり簡単な方法です。

古い...

MyObject myObject

新着...

MyObject &myObject()
{
  static MyObject myActualObject;
  return myActualObject;
}

もちろん、アプリケーションがマルチスレッドの場合、これは最初よりも多くの問題を引き起こす可能性があります...

1
Roddy

Gimpel Software(www.gimpel.com)は、PC-Lint/FlexeLint静的解析ツールがそのような問題を検出すると主張しています。

私は彼らのツールで良い経験をしましたが、この特定の問題ではありませんでした。

1
Martin Vuille

他の答えは正しいです。オブジェクトのゲッターは.cppファイルに実装する必要があり、静的ではないことを追加したかっただけです。ヘッダーファイルに実装すると、オブジェクトは呼び出し元の各ライブラリ/フレームワークに作成されます。

0
Ryan

プロジェクトがVisual Studioにある場合(VC++ Express 2005およびVisual Studio 2008 Proでこれを試しました):

  1. クラスビューを開く(メインメニュー->表示->クラスビュー)
  2. ソリューションの各プロジェクトを展開し、「グローバル関数と変数」をクリックします

これにより、fiascoの影響を受けるすべてのグローバルの適切なリストが得られます。

最後に、より良いアプローチは、これらのオブジェクトをプロジェクトから削除しようとすることです(時々、言うより簡単です)。

0
Warren Stevens