17

今朝、同僚と静的変数の初期化順序について話し合いました。彼はNifty/Schwarzカウンターについて言及しましたが、私は(一種の)困惑しています。私はそれがどのように機能するかを理解していますが、これが技術的に言えば、標準に準拠しているかどうかはわかりません。

次の3つのファイルを想定します(最初の2つはMore C ++ Idiomsからコピーパスタされています)。


//Stream.hpp
class StreamInitializer;

class Stream {
   friend class StreamInitializer;
 public:
   Stream () {
   // Constructor must be called before use.
   }
};
static class StreamInitializer {
  public:
    StreamInitializer ();
    ~StreamInitializer ();
} initializer; //Note object here in the header.

//Stream.cpp
static int nifty_counter = 0; 
// The counter is initialized at load-time i.e.,
// before any of the static objects are initialized.
StreamInitializer::StreamInitializer ()
{
  if (0 == nifty_counter++)
  {
    // Initialize Stream object's static members.
  }
}
StreamInitializer::~StreamInitializer ()
{
  if (0 == --nifty_counter)
  {
    // Clean-up.
  }
}

// Program.cpp
#include "Stream.hpp" // initializer increments "nifty_counter" from 0 to 1.

// Rest of code...
int main ( int, char ** ) { ... }

...そしてここに問題があります!2つの静的変数があります。

  1. Stream.cpp;の"nifty_counter" と
  2. の「初期化子」Program.cpp

2つの変数はたまたま2つの異なるコンパイル単位にあるため、のコンストラクターが呼び出される前に0に初期化される(AFAIK)公式保証はありません。nifty_counterinitializer

私は2つの簡単な解決策を、これが「機能する」理由の2つと考えることができます。

  1. 最新のコンパイラは、2つの変数間の依存関係を解決し、実行可能ファイル内でコードを適切な順序で配置するのに十分なほど賢いです(ほとんどありません)。
  2. nifty_counter記事にあるように、実際には「ロード時」に初期化され、その値は実行可能ファイルの「データセグメント」にすでに配置されているため、常に「コードが実行される前」に初期化されます(可能性が高い)。

これらは両方とも、非公式でありながら可能な実装に依存しているように私には思えます。この標準は準拠していますか、それとも「機能する可能性が非常に高い」ので、心配する必要はありませんか?

4

2 に答える 2

22

動作することが保証されていると思います。標準 ($3.6.2/1) によると、「静的ストレージ期間 (3.7.1) を持つオブジェクトは、他の初期化が行われる前にゼロで初期化 (8.5) されなければならない」。

静的な保存期間があるため、翻訳単位間の分散に関係なく、 が作成さnifty_counterれる前に初期化されます。initializer

編集: 問題のセクションを読み直し、@Tadeusz Kopec のコメントからの入力を検討した後、現在の状態で適切に定義されているかどうかについては確信が持てませんが、明確に定義されていることを確認するの非常に簡単です:の定義からの初期化nifty_counterなので、次のようになります。

static int nifty_counter;

静的な保存期間があるため、初期化子を指定しなくてもゼロ初期化されます。初期化子を削除すると、ゼロ初期化後に行われる他の初期化についての疑いがなくなります。

于 2011-04-11T14:33:10.407 に答える
3

この例には、Stream の構築を回避する方法が欠けていると思います。これは多くの場合、移植性がありません。気の利いたカウンターに加えて、初期化子の役割は次のようなものを構築することです。

extern Stream in;

1 つのコンパイル単位がそのオブジェクトに関連付けられたメモリを持っている場合、インプレース new 演算子が使用される前に特別なコンストラクターがあるかどうか、または競合を避けるために別の方法でメモリが割り当てられているのを見たことがあります。このストリームに no-op コンストラクターがあり、初期化子が最初に呼び出されるか、no-op コンストラクターが呼び出されるかの順序が定義されていないように思えます。

バイトの領域を割り当てることは、多くの場合移植性がありません。たとえば、gnu iostream の場合、cin のスペースは次のように定義されます。

typedef char fake_istream[sizeof(istream)] __attribute__ ((aligned(__alignof__(istream))))
...
fake_istream cin;

llvm は以下を使用します:

_ALIGNAS_TYPE (__stdinbuf<char> ) static char __cin [sizeof(__stdinbuf <char>)];

どちらも、オブジェクトに必要なスペースについて一定の仮定をしています。Schwarz Counter が新しい配置で初期化される場所:

new (&cin) istream(&buf)

実際には、これは移植性がないように見えます。

gnu、microsoft、AIX などの一部のコンパイラには、静的初期化子の順序に影響を与えるコンパイラ拡張機能があることに気付きました。

  • Gnu の場合:フラグを使用init-priorityして を有効にし、を使用します。-f__attribute__ ((init_priority (n)))
  • Microsoft コンパイラを搭載した Windows には、#pragma ( http://support.microsoft.com/kb/104248 )があります。
于 2013-08-21T22:04:06.467 に答える