13

静的オブジェクトの初期化について学習しようとしています。静的初期化は、定数式とconstexpr. 動的な初期化はかなり難しいようです。

[basic.start.init]/4

メインの最初のステートメントの前に、静的記憶域期間を持つ非ローカル変数の動的初期化が行われるかどうかは、実装によって定義されます。初期化が main の最初のステートメントの後のある時点まで延期される場合、初期化される変数と同じ変換単位で定義された関数または変数の最初の odr-use (3.2) の前に発生するものとします。

脚注 34

副作用のある初期化を持つ静的ストレージ期間を持つ非ローカル変数は、odr で使用されていなくても初期化する必要があります (3.2、3.7.1)。

[basic.start.init]/5

スレッドの初期関数の最初のステートメントの前に、静的またはスレッド記憶期間を持つ非ローカル変数の動的初期化が行われるかどうかは、実装によって定義されます。初期化がスレッドの初期関数の最初のステートメントの後のある時点まで延期される場合、変数と同じ変換単位で定義されたスレッド記憶期間を持つ変数の最初の ODR 使用 (3.2) の前に発生するものとします。初期化する。

「スレッドの初期関数」は、std::thread で開始されたスレッドだけでなく、main を参照していると思います。

h1.h

#ifndef H1_H_
#define H1_H_

extern int count;

#endif

tu1.cpp

#include "h1.h"

struct S
{
   S()
   {
      ++count;
   }
};

S s;

tu2.cpp

#include "h1.h"

int main(int argc, char *argv[])
{
   return count;
}

tu3.cpp

#include "h1.h"

int count;

したがって、コンパイラが動的な初期化を延期する場合、脚注 34 は、sある時点で初期化する必要があると述べているようです。翻訳単位には動的な初期化を行う変数が他にないため、tu1 の変数の初期化を強制するために odr-use を使用する変数は他にありません。どの時点で初期化されたことがs保証されますか?

main は 1 を返すことが保証されていますか? また、1 を返すことが保証されないように、このプログラムを変更する方法はありますか? または、保証されていない場合、保証されるようにこのプログラムを変更する方法はありますか?


sの定義がとは異なる翻訳単位になるように、コードを分割しましたmain。これにより、mainodr が使用されているかどうかという問題が回避されます。sそれが翻訳単位の唯一のオブジェクトであることを考えると、それはmain1 を返すことが保証されていますか?

4

3 に答える 3

5

この言葉遣いはすべて、動的に読み込まれたライブラリで何が起こるかを説明するためにあると思いますが、明示的に名前を付けることはありません。

私がそれをどのように解釈するかを要約すると、静的ストレージ期間と動的初期化を持つ非ローカル変数は次のようになります。

  1. 翻訳単位内の何かを最初に使用する前に初期化されます。
  2. おそらく、開始前mainですが、その後かもしれません。

私は脚注 34 を次のように解釈します (ただし、脚注は規範的ではないことに注意してください)。

TU 内の何かが ord で使用される場合、odr で使用されていない変数であっても、副作用のある初期化を持つ静的ストレージ期間を持つすべての非ローカル変数を初期化する必要があります。

そのため、何も ord が使用されていない TU がある場合、その動的な初期化は発生しない可能性があります。

h1.h

extern int count;
struct S
{
    S();
};

h1.cpp

#include "h1.h"

int count;
S::S()
{
   ++count;
}

h2.cpp

#include "h1.h"
S s;

main.cpp

#include "h1.h"
#include <stdio.h>
int main()
{
    printf("%d\n", count);
}

これは 0 または 1 を出力します: TU h2 内のものは決して odr で使用されないため、コードの初期化がsいつ行われるかは不明です。

当然、正常なコンパイラはsメインの前に初期化されるため、確実に出力されます1

$ g++ main.cpp h2.cpp h1.cpp -o test1
$ ./test1
1

h2.cppここで、それが共有ライブラリにあると想像してください。

$ g++ -shared -fPIC h2.cpp -o h2.so

メインファイルは次のとおりです。

main2.cpp

#include "h1.h"
#include <dlfcn.h>
#include <stdio.h>

int main()
{
    printf("%d\n", count);
    dlopen("./h2.so", RTLD_NOW);
    printf("%d\n", count);
    return 0;
}

コンパイルして実行します。

$ g++ -shared -fPIC h2.cpp -o h2.so
$ g++ -rdynamic main.cpp h1.cpp -ldl -o test2
$ ./test2
0
1

見る?の初期化sが遅れています! 良い点は、動的に読み込まれたライブラリを最初に読み込まずに参照することは不可能であり、読み込むと動的な初期化がトリガーされることです。だから、すべてが順調です。

使用がごまかしていると思われる場合dlopenは、共有ライブラリの遅延ロードをサポートするコンパイラ (VC++ など) があることを思い出してください。この場合、ライブラリをロードするためのシステム コールは、最初に必要になったときにコンパイラによって自動的に生成されます。

于 2013-09-03T21:08:32.737 に答える
3

Without searching for the right pages in the definition I can say that your program is guaranteed to return 1. Every static or global initialization is done before the first command in the main. Global variables are initializes first an then the constructors of global objects are executed. Statics within a function/method scope a initialized before first use. But there is a trap:

int count;

struct A
{
   A()
   {
     count=5;
   }
};

struct B
{
   B()
   {
     count=count*2;
   }
};


A a;
B b;

void main(void)
{
  return count;
}

Ben Voigt の解説で述べられているように、結果は、両方のインスタンスが同じ翻訳単位で作成された場合に定義されます。したがって、私のサンプルでは、​​結果は 10 です。インスタンスが別のファイルで作成された場合 (および別の .obj ファイルに個別にコンパイルされた場合)、結果は定義されません。

于 2013-09-03T19:54:36.017 に答える
0

「初期化される変数と同じ変換単位で定義された関数または変数の最初の ODR 使用」には、初期化される変数が含まれます。

于 2013-09-03T19:54:47.237 に答える