関数レベルの静的変数はいつ割り当て/初期化されますか?


89

グローバルに宣言された変数は、プログラムの開始時に割り当てられる(そして、該当する場合は初期化される)と確信しています。

int globalgarbage;
unsigned int anumber = 42;

しかし、関数内で定義された静的なものはどうですか?

void doSomething()
{
  static bool globalish = true;
  // ...
}

globalish割り当てられるスペースはいつですか?プログラムがいつ始まるか推測しています。しかし、それも初期化されますか?またはdoSomething()最初に呼び出されたときに初期化されますか?

回答:


91

私はこれに興味があったので、次のテストプログラムを作成し、g ++バージョン4.1.2でコンパイルしました。

include <iostream>
#include <string>

using namespace std;

class test
{
public:
        test(const char *name)
                : _name(name)
        {
                cout << _name << " created" << endl;
        }

        ~test()
        {
                cout << _name << " destroyed" << endl;
        }

        string _name;
};

test t("global variable");

void f()
{
        static test t("static variable");

        test t2("Local variable");

        cout << "Function executed" << endl;
}


int main()
{
        test t("local to main");

        cout << "Program start" << endl;

        f();

        cout << "Program end" << endl;
        return 0;
}

結果は私が期待したものではありませんでした。静的オブジェクトのコンストラクターは、関数が初めて呼び出されるまで呼び出されませんでした。出力は次のとおりです。

global variable created
local to main created
Program start
static variable created
Local variable created
Function executed
Local variable destroyed
Program end
local to main destroyed
static variable destroyed
global variable destroyed

29
説明として、静的変数は、実行が最初に宣言にヒットしたときに初期化されます。含まれている関数が呼び出されたときではありません。関数の最初にstaticがあるだけの場合(例では)、これらは同じですが、必ずしもそうではありません。たとえば、if(...){static MyClass x; ...} 'の場合、ifステートメントの条件がfalseと評価された場合、その関数の最初の実行中に' x 'はALLで初期化されません。
EvanED 2014

4
しかし、静的変数が使用されるたびに、プログラムはそれが以前に使用されたかどうかをチェックする必要があります。そうでない場合は初期化する必要があるためです。その場合、その種のことは少し厄介です。
HelloGoodbye 2014

完璧なイラスト
Des1gnWizard

@veio:はい、初期化はスレッドセーフです。詳細については、その質問を参照してください。stackoverflow.com/questions/23829389/...
レミ

2
@HelloGoodbye:はい、実行時にオーバーヘッドが発生します。また、その質問を参照してください。stackoverflow.com/questions/23829389/...
レミ

53

C ++標準からの関連する言い回し:

3.6.2非ローカルオブジェクトの初期化[basic.start.init]

1

静的ストレージ期間(basic.stc.static)を持つオブジェクトのストレージは、他の初期化が行われる前にゼロ初期化(dcl.init)されます。定数式(expr.const)で初期化された静的ストレージ期間を持つPODタイプ(basic.types)のオブジェクトは、動的初期化が行われる前に初期化されます。同じ翻訳単位で定義され、動的に初期化された静的ストレージ期間を持つ名前空間スコープのオブジェクトは、その定義が翻訳単位に現れる順序で初期化されます。[注: dcl.init.aggr 集計メンバーが初期化される順序を説明します。ローカル静的オブジェクトの初期化はstmt.dclに記述されています。]

[以下のテキストを追加して、コンパイラ作成者の自由を追加してください]

6.7宣言ステートメント[stmt.dcl]

...

4

静的ストレージ期間(basic.stc.static)を持つすべてのローカルオブジェクトのゼロ初期化(dcl.init)は、他の初期化が行われる前に実行されます。定数式で初期化された静的ストレージ期間を持つPODタイプ(basic.types)のローカルオブジェクトは、そのブロックに最初に入る前に初期化されます。実装は、名前空間スコープ(basic.start.init)。そうでない場合、そのようなオブジェクトは、コントロールがその宣言を最初に通過するときに初期化されます。このようなオブジェクトは、初期化が完了すると初期化されたと見なされます。例外をスローして初期化が終了した場合、初期化は完了していないため、次にコントロールが宣言に入るときに再試行されます。オブジェクトの初期化中にコントロールが宣言に(再帰的に)再入力した場合の動作は未定義です。[ 例:

      int foo(int i)
      {
          static int s = foo(2*i);  // recursive call - undefined
          return i+1;
      }

- 例を終了 ]

5

静的ストレージ期間を持つローカルオブジェクトのデストラクタは、変数が作成された場合にのみ実行されます。[注: basic.start.term は、静的ストレージ期間を持つローカルオブジェクトが破棄される順序を説明しています。]


これは私の質問に答えたものであり、受け入れられた回答とは異なり、「事例証拠」には依存していません。私は特に、静的に初期化機能ローカル静的オブジェクトのコンストラクタで例外のこの言及を探していた:If the initialization exits by throwing an exception, the initialization is not complete, so it will be tried again the next time control enters the declaration.
Bensge

26

すべての静的変数のメモリは、プログラムのロード時に割り当てられます。ただし、ローカル静的変数は、プログラムの起動時ではなく、最初に使用されたときに作成および初期化されます。これについての良い読みと、一般的な統計がここにあります。一般に、これらの問題のいくつかは実装に依存すると思います。特に、メモリ内のこの場所がどこにあるかを知りたい場合は特にそうです。


2
完全ではありませんが、ローカルの静的変数が割り当てられ、「プログラムの読み込み時」にゼロで初期化されます(引用符で囲まれています。これも正しくないためです)。次に、それらが含まれている関数に初めて入るときに再初期化されます。
Mooing Duck

そのリンクは7年後に壊れているようです。
Steve

1
うん、リンクが壊れた。ここではアーカイブです:web.archive.org/web/20100328062506/http://www.acm.org/...
ユージン

10

コンパイラーはfooプログラムのロード時に関数で定義された静的変数を割り当てますが、コンパイラーは関数にいくつかの追加の命令(マシンコード)を追加しfooて、初めて呼び出されたときにこの追加のコードが静的変数(たとえば、該当する場合はコンストラクタを呼び出します)。

@Adam:これは、コンパイラによるコードのインジェクションの舞台裏であり、あなたが見た結果の理由です。


5

Adam Pierceからのコードをもう一度テストして、クラスの静的変数とPODタイプの2つのケースを追加しました。私のコンパイラは、Windows OS(MinGW-32)のg ++​​ 4.8.1です。結果は、クラスの静的変数はグローバル変数と同じように扱われます。そのコンストラクタは、メイン関数に入る前に呼び出されます。

  • 結論(g ++、Windows環境の場合):

    1. クラスのグローバル変数静的メンバー:コンストラクターは、メイン関数(1)に入る前に呼び出されます。
    2. ローカル静的変数:コンストラクタは、実行が最初に宣言に達したときにのみ呼び出されます。
    3. 場合はローカル静的変数はPOD型であり、それはまた入る前に初期化される主な機能(1) 。PODタイプの例:static int number = 10;

(1):正しい状態は、「同じ変換単位の関数が呼び出される前」です。ただし、以下の例のように、単純な場合はメイン関数です。

インクルード<iostream>

#include < string>

using namespace std;

class test
{
public:
   test(const char *name)
            : _name(name)
    {
            cout << _name << " created" << endl;
    }

    ~test()
    {
            cout << _name << " destroyed" << endl;
    }

    string _name;
    static test t; // static member
 };
test test::t("static in class");

test t("global variable");

void f()
{
    static  test t("static variable");
    static int num = 10 ; // POD type, init before enter main function

    test t2("Local variable");
    cout << "Function executed" << endl;
}

int main()
{
    test t("local to main");
    cout << "Program start" << endl;
    f();
    cout << "Program end" << endl;
    return 0;
 }

結果:

static in class created
global variable created
local to main created
Program start
static variable created
Local variable created
Function executed
Local variable destroyed
Program end
local to main destroyed
static variable destroyed
global variable destroyed
static in class destroyed

Linux envでテストした人はいますか?


3

静的変数はコードセグメント内に割り当てられます。これらは実行可能イメージの一部であるため、初期化済みでマップされます。

関数スコープ内の静的変数は同じように扱われ、スコープは純粋に言語レベルの構造です。

このため、静的変数が未定義の値ではなく0に初期化されることが保証されます(他に何か指定しない限り)。

初期化には他にもいくつかメリットがあります。たとえば、共有セグメントを使用すると、実行中の実行可能ファイルの異なるインスタンスが同時に同じ静的変数にアクセスできます。

C ++(グローバルスコープ)では、静的オブジェクトは、Cランタイムライブラリの制御下で、プログラム起動の一部として呼び出されるコンストラクターを持っています。Visual C ++では、少なくともオブジェクトが初期化される順序は、init_segプラグマによって制御できます。


4
この質問は、関数スコープの統計についてです。少なくとも、自明でないコンストラクタがある場合、関数への最初のエントリで初期化されます。より具体的には、その行に達したとき。
アダムミッツ

真-しかし、質問は変数に割り当てられたスペースについて話し、単純なデータ型を使用します。スペースは引き続きコードセグメントに割り当てられています
Rob Walker

ここで、コードセグメントとデータセグメントがどのように重要であるかはわかりません。OPからの説明が必要だと思います。彼は「そして該当する場合は初期化しました」と言いました。
Adam Mitz

5
変数がコードセグメント内に割り当てられることはありません。このようにすると、書き込みできなくなります。
botismarius 2008

1
静的変数は、初期化されているかどうかに応じて、データセグメントまたはbssセグメントに領域が割り当てられます。
EmptyData 2017年

3

それともdoSomething()が最初に呼び出されたときに初期化されますか?

はい、そうです。これにより、たとえば、try / catchブロック内など、適切にグローバルアクセスされるデータ構造を初期化できます。例えばの代わりに

int foo = init(); // bad if init() throws something

int main() {
  try {
    ...
  }
  catch(...){
    ...
  }
}

あなたは書ける

int& foo() {
  static int myfoo = init();
  return myfoo;
}

そしてそれをtry / catchブロック内で使用します。最初の呼び出しで、変数が初期化されます。次に、最初と次の呼び出しで、その値が(参照により)返されます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.