8

C99 の厳密なエイリアシング規則を破ることなく、共有メモリ バッファを実装するのに苦労しています。

一部のデータを処理するコードがあり、操作するために「スクラッチ」メモリが必要であるとします。次のように書くことができます:

void foo(... some arguments here ...) {
  int* scratchMem = new int[1000];   // Allocate.
  // Do stuff...
  delete[] scratchMem;  // Free.
}

次に、スクラッチ バッファーも必要とする他の機能を実行する別の関数があります。

void bar(...arguments...) {
  float* scratchMem = new float[1000];   // Allocate.
  // Do other stuff...
  delete[] scratchMem;  // Free.
}

問題は、操作中に foo() と bar() が何度も呼び出される可能性があり、あちこちにヒープ割り当てがあると、パフォーマンスとメモリの断片化の点で非常に悪い場合があることです。明らかな解決策は、適切なサイズの共通の共有メモリ バッファを一度割り当ててから、BYOB スタイルの引数として foo() と bar() に渡すことです。

void foo(void* scratchMem);
void bar(void* scratchMem);

int main() {
  const int iAmBigEnough = 5000;
  int* scratchMem = new int[iAmBigEnough];

  foo(scratchMem);
  bar(scratchMem);

  delete[] scratchMem;
  return 0;
}

void foo(void* scratchMem) {
  int* smem = (int*)scratchMem;
  // Dereferencing smem will break strict-aliasing rules!
  // ...
}

void bar(void* scratchMem) {
  float* smem = (float*)scratchMem;
  // Dereferencing smem will break strict-aliasing rules!
  // ...
}


2 つの質問があると思います:
- エイリアシング ルールに違反していない共有共通スクラッチ メモリ バッファを実装するにはどうすればよいですか?
- 上記のコードは厳密なエイリアシング ルールに違反していますが、エイリアスを使用しても「害」はありません。したがって、正常なコンパイラで (最適化された) コードが生成され、それでも問題が発生する可能性はありますか?

ありがとう

4

3 に答える 3

3

実際、あなたが書いたものは厳密なエイリアシング違反ではありません。

C++11 仕様 3.10.10 は次のように述べています。

プログラムが、次の型以外の glvalue を介してオブジェクトの格納された値にアクセスしようとした場合、動作は未定義です。

したがって、未定義の動作を引き起こすのは、格納された値へのポインタを作成するだけでなく、格納された値にアクセスすることです。あなたの例は何にも違反していません。次のステップを実行する必要があります: float badValue = smem[0]. smem[0] は共有バッファーから格納された値を取得し、エイリアシング違反を引き起こします。

もちろん、設定する前に smem[0] を取得するだけではありません。最初に書き込みます。同じメモリへの代入は格納された値にアクセスしないため、エイリアシングはありません。ただし、オブジェクトがまだ生きている間にオブジェクトの上に上書きすることは違法です。安全であることを証明するには、3.8.4 からのオブジェクトの寿命が必要です。

プログラムは、オブジェクトが占有する記憶域を再利用するか、非自明なデストラクタを使用してクラス型のオブジェクトのデストラクタを明示的に呼び出すことにより、オブジェクトの有効期間を終了できます。自明でないデストラクタを持つクラス型のオブジェクトの場合、プログラムは、オブジェクトが占有するストレージが再利用または解放される前に、デストラクタを明示的に呼び出す必要はありません。... [デストラクタを呼び出さないことの結果について続けます]

あなたはPOD型を持っているので、簡単なデストラクタなので、口頭で「intオブジェクトはすべて寿命が来ています。フロート用のスペースを使用しています」と簡単に宣言できます。その後、フロート用にスペースを再利用すると、エイリアシング違反は発生しません。

于 2013-09-05T06:41:52.330 に答える
1

オブジェクトを一連のバイトとして解釈することは常に有効です (つまり、任意のオブジェクト ポインターを char の配列の最初の要素へのポインターとして扱うことはエイリアシング違反ではありません)。十分に大きく、適切に配置されたメモリ。

したがって、s の大きな配列char(任意の符号) を割り当てて、位置合わせされたオフセットを見つけることができますalignof(maxalign_t)。そこで適切なオブジェクトを作成すると、そのポインターをオブジェクト ポインターとして解釈できるようになりました (たとえば、C++ でplacement-new を使用)。

もちろん、既存のオブジェクトのメモリに書き込まないようにする必要があります。実際、オブジェクトの寿命は、オブジェクトを表すメモリに何が起こるかに密接に関係しています。

例:

char buf[50000];

int main()
{
    uintptr_t n = reinterpret_cast<uintptr_t>(buf);
    uintptr_t e = reinterpret_cast<uintptr_t>(buf + sizeof buf);

    while (n % alignof(maxalign_t) != 0) { ++n; }

    assert(e > n + sizeof(T));

    T * p = :: new (reinterpret_cast<void*>(n)) T(1, false, 'x');

    // ...

    p->~T();
}

mallocまたはによって取得されたメモリnew char[N]は、常に最大のアラインメントのためにアラインされることに注意してください (ただし、それ以上ではありません。オーバーアラインされたアドレスを使用することもできます)。

于 2013-09-04T22:19:04.503 に答える