68

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

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

4

12 に答える 12

77

初期化の解決順序:

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

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

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

マルチスレッドの問題:

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

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

ただし、C ++ 03は、静的関数オブジェクトの構築がスレッドセーフであることを公式に保証するものではありません。したがって、技術的には、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.
        }
};
于 2008-12-02T22:51:35.993 に答える
32

この問題を追跡するためのコードを少し書きました。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

ソリューション内のすべての .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 ファイルにあるはずです。少なくとも、これはブレークポイントを設定するのに適した場所になります。このコードは絶対に最初に実行する必要があるためです (その後、コードをステップ実行して、初期化されているすべてのグローバルを確認できます)。 .

ノート:

  • 「FIASCO_FINDER」マクロをファイルのできるだけ先頭に配置することが重要です。他の #includes の下に配置すると、現在のファイルを特定する前にクラッシュするリスクがあります。

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

于 2010-08-04T02:42:47.533 に答える
15

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

次に、各関数にステップインして、ファイルと関数名を取得します (デバッグ情報をオンにしてコンパイルしたと仮定します)。名前を取得したら、関数を終了し ( に戻ります_initterm)、 が終了するまで続行し_inittermます。

これにより、コード内のものだけでなく、すべての静的初期化子が得られます。これは、完全なリストを取得する最も簡単な方法です。制御できないもの (サードパーティ ライブラリ内のものなど) を除外できます。

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

于 2008-12-02T21:57:16.307 に答える
5

コンパイラによって生成される C++ を本質的に「初期化」するコードがあります。このコード/コール スタックをその時点で見つける簡単な方法は、コンストラクターで NULL を逆参照する何かを使用して静的オブジェクトを作成することです。デバッガーを中断して、少し調べます。MSVC コンパイラは、静的初期化のために反復される関数ポインターのテーブルを設定します。このテーブルにアクセスして、プログラムで行われているすべての静的初期化を確認できる必要があります。

于 2011-01-11T15:01:16.067 に答える
5

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

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

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

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

編集:冗長な静的オブジェクトを削除しました。理由はわかりませんが、元の例で静的を一緒にする2つの方法を混合して一致させました。

于 2008-12-02T20:51:35.563 に答える
4

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

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

1) 重要なコンストラクターを持つすべてのグローバルを見つけて、それらをリストに入れます。

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

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

4) ステップ 1 で生成されたリストがなくなるまで、ステップ 2 と 3 を繰り返します。

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

于 2009-05-05T22:23:19.337 に答える
1

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

私は彼らのツールについては良い経験をしましたが、この特定の問題については経験していなかったので、彼らがどれだけ役立つかを保証することはできません。

于 2009-05-05T22:34:19.607 に答える
1

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

これで、プログラムが動作する (万歳!) か、循環依存関係があるために無限ループに陥るか (再設計が必要)、または次のバグに進むかのいずれかです。

于 2008-12-02T20:52:16.640 に答える
1

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

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

シングルトン パターンには多くの批判が寄せられますが、怠惰な「必要に応じた」構成は、現在および将来の問題の大部分をかなり簡単に修正する方法です。

年...

MyObject myObject

新着...

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

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

于 2008-12-02T20:53:14.437 に答える
0

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

于 2011-06-28T09:24:03.687 に答える
0

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

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

これにより、大失敗の対象となるすべてのグローバルの適切なリストが得られるはずです。

最終的には、これらのオブジェクトをプロジェクトから削除することをお勧めします (言うは易く行うは難しです)。

于 2010-07-22T02:32:53.537 に答える