13

この例では、正しさglobal_valueを宣言する必要がありvolatileますか?

int global_value = 0;

void foo () {
    ++ global_value;
}

void bar () {
    some_function (++global_value);
    foo ();
    some_function (++global_value);
}

私の理解では、シグナルによって変更できる(そして特にスレッドセーフではないvolatile)マップされたメモリと変数へのポインタを「意図」していますが、次のようにコンパイルされる可能性があることは容易に想像できます。bar

push EAX
mov EAX, global_value
inc EAX
push EAX
call some_function
call foo
inc EAX
push EAX
call some_function
mov global_value, EAX
pop EAX

これは明らかに正しくありませんがvolatile、C抽象マシンによれば有効であると私は信じていません。私は間違っていますか、それとも有効ですか?

もしそうなら、それvolatileは日常的に見過ごされているように私には思えます。これは新しいことではありません


拡張例

void baz (int* i) {
    some_function (++*i);
    foo ();
    some_function (++*i);
}

int main () {
    baz (&global_value);
}

bar正しいdont-cache-global_value実装にコンパイルされることが保証されている場合でも、baz同様に正しくなりますか、それとも?の不揮発性値をキャッシュでき*iますか?

4

5 に答える 5

12

いいえ、volatileここではキーワードは必要ありません。global_valueは関数の外部に表示されるためbar、コンパイラは、別の関数が呼び出されても同じままであると想定してはなりません。

[更新2011-07-28]すべてを証明する素晴らしい引用を見つけました。それはISOC99、5.1.2.3p2にありますが、私は怠惰すぎてここに完全にコピーすることはできません。それは言う:

シーケンスポイントと呼ばれる実行シーケンスの特定の指定されたポイントで、前の評価のすべての副作用が完了し、後続の評価の副作用が発生していないものとします。

シーケンスポイントは次のとおりです。

  • 引数が評価された後の関数の呼び出し(6.5.2.2)。
  • 完全な式の終わり:[...]式ステートメントの式(6.8.3); [...]

そこにあなたの証拠があります。

于 2011-07-28T11:38:09.643 に答える
5

volatile関与longjmp、シグナルハンドラー、メモリマップドデバイスドライバー、および独自の低レベルマルチスレッド同期プリミティブの作成のみを使用します。ただし、この最後の使用にvolatileは十分ではなく、必要ない場合もあります。同期には、asm(またはコンパイラ固有またはC1xアトミック)も必要です。

volatileあなたが尋ねたコードを含む他の目的には役に立ちません。

于 2011-07-28T11:59:23.763 に答える
4

Rolandが言うように、「プログラムが何かを変更する場合、それはオブジェクトが抽象マシンで変更されることを意味します。プログラムが値を使用する場合、それは任意の値を使用することを意味します。オブジェクトは抽象マシンにあります」。

volatileメモリへの読み取りと書き込みの数と順序を制御しますが、がなくてもvolatile、最適化として値をキャッシュする実装は、抽象マシンの動作を尊重する必要があります。それが「あたかも」ルールが言っていることなので、私にとって「想像しやすい」ものではない、従わない最適化;-)提案された出力コードは、「書き込みが行われる可能性がある」と言うのと同じくらい明らかに間違っています。 L1キャッシュを更新またはダーティすることなくメモリに保存されるため、今後の読み取りではキャッシュ内の古い値が引き続き表示されます。シングルコアではなく、そのように動作するキャッシュが壊れてしまうため、そうではありません。

を呼び出しstrcpyてから宛先バッファの内容を調べると、コンパイラは、レジスタに格納されているそのバイトの前の値を使用して「最適化」することはできません。strcpyかかりませんvolatile char *。同様に、でglobal_valueある必要はありませんvolatile

マルチスレッドコードでは、「その後」、つまり読み取りが書き込みの「後に」発生し、したがって新しい値を「見る」かどうかが同期プリミティブによって定義されるという混乱が生じる可能性があります。一部の実装では、volatile実装固有の保証により、同期と関係があります。

シングルスレッドコード、およびCおよびC ++標準では、「and then」はシーケンスポイントによって定義され、指定されたコードにはその多くが含まれています。

于 2011-07-28T12:01:24.400 に答える
1

いいえ。グローバル変数は常に揮発性として宣言されるべきではありません。

他のスレッドによって変更される可能性があり、メモリの並べ替えの問題やコンパイラ命令の並べ替えに悩まされる可能性がある場合にのみ、揮発性である必要があります。それでも、適切なミューテックスがあれば、それは必要ありません。ただし、通常、グローバル変数をミューテックスする必要がある場合は、設計が不適切である可能性があります。

編集:それを揮発性にすることは、グローバル変数がスレッドセーフであることを意味しません!

他の典型的な使用法は、メモリが通常とは異なる方法でアクセスされる場合です。たとえば、組み込みマイクロにDMAマップトメモリがある場合などです。

于 2011-07-28T11:40:44.270 に答える
0

この例では、揮発性は必要ありません。たとえば、some_function()が何かを出力する場合、asmリストはc ++マシンの観察可能な動作を変更し、標準に違反しているように見えます。

コンパイラのバグだと思います。GCCアセンブラの出力は次のとおりです。

.cfi_def_cfa_register 5
subl    $24, %esp
.loc 1 67 0
movl    global_value, %eax
addl    $1, %eax
movl    %eax, global_value
movl    global_value, %eax
movl    %eax, (%esp)
call    _Z13some_functioni
.loc 1 68 0
call    _Z3foov
.loc 1 69 0
movl    global_value, %eax
addl    $1, %eax
movl    %eax, global_value
movl    global_value, %eax
movl    %eax, (%esp)
call    _Z13some_functioni
.loc 1 70 0
leave
.cfi_restore 5

global_valueは、期待どおり、関数呼び出しの間に再ロードされます

また、揮発性物質スレッドセーフにも使用できます。すべての場合にv-qualifierだけ ではスレッドセーフには不十分です(原子性とメモリバリアにさらに注意が必要な場合もありますが、スレッド間通信変数揮発性である必要があります...

[編集済み]: ...繰り返し読み取られ、読み取りの間に別のスレッドによって変更される可能性がある場合。ただし、これは、同期ロック(ミューテックスなど)が使用されている場合には当てはまりません。ロックは、変数が同時アクティビティによって変更されないことを保証するためです)(Rのおかげで)

于 2011-07-28T12:07:35.987 に答える