5

次の C++ オブジェクトを定義するとします。

class AClass
{
public:
    AClass() : foo(0) {}
    uint32_t getFoo() { return foo; }
    void changeFoo() { foo = 5; }
private:
    uint32_t foo;
} aObject;

オブジェクトは、T1 と T2 の 2 つのスレッドによって共有されます。T1 は常にgetFoo()ループ内で呼び出して番号を取得しています (changeFoo()以前に呼び出されていない場合は常に 0 になります)。ある時点で、T2 がchangeFoo()それを変更するために呼び出します (スレッド同期なし)。

T1 がこれまでに取得した値が、最新のコンピューター アーキテクチャとコンパイラで 0 または 5 と異なる可能性はありますか? これまでに調査したすべてのアセンブラー コードは、32 ビット メモリの読み取りと書き込みを使用していました。これにより、操作の整合性が保たれているようです。

他のプリミティブ型はどうですか?

実用的とは、理論的にこれ (または別のコードで同様の状況) が可能である既存のアーキテクチャまたは標準準拠のコンパイラの例を示すことができることを意味します。私は現代という言葉を少し主観的に残しています。


編集:多くの人が、5 が読まれることを期待すべきではないことに気付いているのを見ることができます。それは私にとって完全に問題なく、私はそうするとは言いませんでした(ただし、この側面を指摘してくれてありがとう)。私の質問は、上記のコードでどのようなデータ整合性違反が発生する可能性があるかということでした。

4

7 に答える 7

11

実際には、私が知る限り、他には何も表示さ0れません (おそらく、32 ビットの奇妙な 16 ビット アーキテクチャではそうではありません)。5int

ただし、実際5にまったく見えるかどうかは保証されません。

私がコンパイラだとします。

そうですか:

while (aObject.getFoo() == 0) {
    printf("Sleeping");
    sleep(1);
}

そんなこと知ってる:

  • printf変わることはできないaObject
  • sleep変わることはできないaObject
  • getFoo変わらないaObject(インライン定義に感謝)

したがって、コードを安全に変換できます。

while (true) {
    printf("Sleeping");
    sleep(1);
}

C++ Standard によると、aObjectこのループ中に他に誰もアクセスしていないためです。

それが未定義の動作の意味です: 期待を膨らませます。

于 2013-02-22T09:14:57.073 に答える
4

実際には、すべての主流の 32 ビット アーキテクチャは、32 ビットの読み取りと書き込みをアトミックに実行します。0 または 5 以外は表示されません。

于 2013-02-22T08:57:40.103 に答える
2

T1 がこれまでに取得した値が、最新のコンピューター アーキテクチャとコンパイラで 0 または 5 と異なる可能性はありますか? 他のプリミティブ型はどうですか?

確かに - データ全体がアトミックな方法で読み書きされるという保証はありません。実際には、部分的な書き込み中に読み取りが発生する可能性があります。何が中断される可能性があり、それがいつ発生するかは、いくつかの変数によって異なります。したがって、実際には、型のサイズと配置が異なると、結果が簡単に変わる可能性があります。当然のことながら、プログラムがプラットフォームからプラットフォームへ移動したり、ABI が変更されたりすると、その差異が生じることもあります。さらに、最適化が追加され、他のタイプ/抽象化が導入されると、観察可能な結果が異なる場合があります。コンパイラは、プログラムの大部分を自由に最適化できます。インスタンスのスコープに応じて、おそらく完全に(OPでは考慮されないさらに別の変数)。

オプティマイザ、コンパイラ、およびハードウェア固有のパイプラインを超えて: カーネルは、このメモリ領域の処理方法にも影響を与える可能性があります。あなたのプログラムは、各オブジェクトのメモリがどこにあるかを保証していますか? おそらくそうではありません。オブジェクトのメモリは別々の仮想メモリ ページに存在する可能性があります。すべてのプラットフォーム/カーネルで一貫した方法でメモリが読み書きされるようにするために、プログラムはどのような手順を実行しますか? (なし、明らかに)

要するに: 抽象マシンによって定義されたルールに従ってプレイできない場合は、その抽象マシンのインターフェイスを使用しないでください (たとえば、C++ の抽象マシンの仕様が本当にニーズに合わない場合は、アセンブリを理解して使用する必要があります。非常にありそうもない)。

これまでに調査したすべてのアセンブラー コードは、32 ビット メモリの読み取りと書き込みを使用していました。これにより、操作の整合性が保たれているようです。

それは「誠実さ」の非常に浅い定義です。あなたが持っているのは(疑似)順次一貫性だけです。同様に、コンパイラはそのようなシナリオであるかのように振る舞うだけでよく、これは厳密な一貫性とはほど遠いものです。期待値が浅いということは、たとえコンパイラーが実際に破壊的な最適化を行わず、何らかの理想または意図に従って読み取りと書き込みを実行したとしても、その結果は実際には役に立たないということです。つまり、プログラムは通常、変更が発生してから「長い間」変更を観察します。

具体的に何を保証できるかを考えると、件名は無関係のままです。

于 2013-02-22T09:38:42.207 に答える
2

あなたが探しているものがわからない。最近のほとんどのアーキテクチャでは、が呼び出された後でも、getFoo()常に が返される可能性が非常に明確にあります。ほぼすべての適切なコンパイラを使用すると、 がタイトなループで呼び出された場合、への呼び出しに関係なく、 が常に同じ値を返すことがほぼ保証されます。0changeFoogetFoo()changeFoo

もちろん、実際のプログラムでは、他の読み取りと書き込みがあり、foo.

最後に、16 ビット プロセッサがあり、一部のコンパイラではアラインされていない可能性があるためuint32_t、アクセスがアトミックにならない可能性があります。(もちろん、1 つのバイトのビットを変更しているだけなので、これは問題にならない可能性があります。)

于 2013-02-22T09:04:43.937 に答える
2

実際unsigned intには(質問を読んでいない人のために)、潜在的な問題は、 an のストア操作がアトミック操作であるかどうかに要約されます。ほとんどの(すべてではないにしても)マシンでコードを書く可能性がありますなれ。

これは標準では規定されていないことに注意してください。これは、対象とするアーキテクチャに固有のものです。0呼び出しスレッドがor以外のものを赤くするシナリオは想像できません5

タイトルに関しては...私はさまざまな程度の「未定義の動作」を認識していません。UBはUB、バイナリ状態です。

于 2013-02-22T08:56:15.217 に答える
1

未定義の動作とは、コンパイラがやりたいことを何でもできることを意味します。彼は基本的に、ピザを注文するなど、好きなことをするようにプログラムを変更できます。

@Matthieu M.の回答を参照してください。これよりも皮肉なバージョンはありません。コメントは議論にとって重要だと思うので、これは削除しません。

于 2013-02-22T08:53:51.403 に答える
0

未定義の動作は、未定義という言葉と同じくらい未定義であることが保証されています。
技術的には、観察可能な動作は単に未定義の動作であるため無意味です。特定の動作を示すためにコンパイラは必要ありません。思った通りに動作するかもしれませんし、コンピュータを燃やすかもしれません。

于 2013-02-22T08:54:02.893 に答える