2009-05-22 11 views
19

私は関数内の静的変数の基本的な実装について興味があります。静的変数の初期化はコンパイラによってどのように実装されますか?

基本型(char、int、doubleなど)の静的変数を宣言して初期値を与えると、コンパイラはその変数の値をmain()前のプログラムが呼び出されます。

void SomeFunction(); 

int main(int argCount, char ** argList) 
{ 
    // at this point, the memory reserved for 'answer' 
    // already contains the value of 42 
    SomeFunction(); 
} 

void SomeFunction() 
{ 
    static int answer = 42; 
} 

しかし、静的変数は、クラスのインスタンスである場合:

class MyClass 
{ 
    //... 
}; 

void SomeFunction(); 

int main(int argCount, char ** argList) 
{ 
    SomeFunction(); 
} 

void SomeFunction() 
{ 
    static MyClass myVar; 
} 

私はそれは関数が呼び出されるのは初めてのことまでは初期化されないであろうことを知っています。コンパイラは、関数が初めて呼び出されるときを知る方法がないので、どのようにしてこの動作が生成されますか?基本的にifブロックを関数本体に導入していますか?

static bool initialized = 0; 
if (!initialized) 
{ 
    // construct myVar 
    initialized = 1; 
} 

答えて

11

私が見たように、関数のローカル静的変数は想像しているとおりに正確に初期化されています。

一般的に、これはではなく、であり、スレッドセーフな方法で行われます。したがって、複数のスレッドから呼び出されるような静的なローカルの関数がある場合は、これを考慮する必要があります。他のスレッドが呼び出される前にメインスレッドで関数を一度呼び出すと、通常はそのトリックが実行されます。

ローカルスタティックの初期化があなたの例のような単純な定数である場合、コンパイラはこれらの回転を通過する必要はありません - 画像の変数を初期化するか、またはmain()のように通常の静的初期化(あなたのプログラムは違いを伝えることができないため)。しかし、関数の戻り値で初期化すると、コンパイラーは初期化が完了したかどうかを示すフラグをテストする必要があります。

+0

何か変更がありましたか?私は静的の各初期化はスレッドセーフであることをC + + 11の後に聞いたことがある。 –

+0

@VictorPolevoy:はい - この回答が書かれたとき、C++ 11は存在しませんでした。 C++ 11では標準でスレッドサポートが含まれていましたが、これはブロックスコープ静的変数(6.7/4)の初期化の説明に追加されました: "変数が初期化されている間にコントロールが同時に宣言に入ると、初期化の完了を待つ」。 –

+1

私は答えとして受け入れられ、10票を持っているので、あなたの答えを編集することをお勧めします。また、この質問は、静的変数の初期化のトピックで最もよく見えます。 –

2

一般的な実装としての初期化済みフラグを含め、すべてについて正しいです。これは基本的に、静的ローカルの初期化がスレッドセーフではない理由と、pthread_onceが存在する理由です。

1つの注意点:コンパイラは、静的ローカル変数が初めて使用されたときに「動作する」ようなコードを出力する必要があります。整数の初期化は副作用がなく(ユーザコードを呼び出さないため)、intを初期化するときにコンパイラに依存します。ユーザーコードは、それが何をしているかを「合法的に」知ることはできません。

明らかに、アセンブリコードを見たり、未定義の動作を引き起こしたり、実際に起こったことから控除を行うことができます。しかし、C++の標準では、その動作が仕様通りのものでないかのように「正当な理由」ではないと主張する有効な根拠としてカウントされていません。

1

初めて関数が呼び出されるまでは初期化されません。コンパイラは、関数が初めて呼び出されるときを知る方法がないので、どのようにしてこの動作が生成されますか?基本的にifブロックを関数本体に導入していますか?

はい、そうです。FWIWは必ずしもスレッドセーフではありません(この関数が同時に2つのスレッドによって「初めて」呼び出される場合)。

そのため、関数内ではなくグローバルスコープで変数を定義することもできます(クラスや名前空間でも、外部リンケージなしで静的にすることもできます)ので、プログラムを実行する前に初期化します時間 "" if "。

10

This questionは同様の地面に覆われていたが、糸の安全性は言及されていなかった。 C++ 0xは、関数の静的な初期化をスレッドセーフにします。

(機能静的にC++0x FCD、6.7/4を参照してください。「変数は初期化されている間に制御が同時に宣言を入力した場合、同時実行は、初期化の 完了を待たなければならない」)

他方言及されていないことは、関数の静的構造が構造の逆の順序で破壊されるということです。そのため、コンパイラはシャットダウン時に呼び出すデストラクタのリストを保持しています(これはatexitが使用するリストと同じかもしれません)。

+2

C++ 0xでのスレッドセーフであることへの参照/引用を教えてもらえますか?私は1つを発見していない。 – ChrisW

1

もう一つのひねりは組み込みコードであり、run-before-main()コード(cinit/whatever)は、初期化されたデータ(静的および非静的の両方)をconstデータセグメントROMに格納する。これは、コードを再読み込み可能なバッキングストア(ディスク)から実行していない場合に便利です。繰り返しますが、これはmain()の前に行われるため、言語の要件に違反しません。

軽いタンジェント:プログラムやコンパイラは、基本的にコードをプロセス内で実行し、オブジェクトをインスタンス化/初期化した後、プロセスをフリーズしてダンプすることができます(Emacs以外ではあまり効果がありません)。 Emacsはこれに似た何かをして大量のelispを読み込み(つまり噛む)、実行中の状態を作業実行可能ファイルとしてダンプして、各呼び出しで解析するコストを避けます。

関連する問題