変数を「揮発性」として宣言するということは、レジスタ変数からではなく、メモリ位置から直接読み書きすることを意味します。「シーケンスポイント」についての知識があります。しかし、私はタイトルに記載されているステートメントを理解していません。
誰かが同じことを説明して、コードスニペットも教えてもらえますか?
変数を「揮発性」として宣言するということは、レジスタ変数からではなく、メモリ位置から直接読み書きすることを意味します。「シーケンスポイント」についての知識があります。しかし、私はタイトルに記載されているステートメントを理解していません。
誰かが同じことを説明して、コードスニペットも教えてもらえますか?
このすべては、C11 5.1.2.3 で説明されています。
プログラムの実行
/--/
揮発性オブジェクトへのアクセス、オブジェクトの変更、ファイルの変更、またはこれらの操作のいずれかを行う関数の呼び出しはすべて、実行環境の状態の変化である副作用です。一般に、式の評価には、値の計算と副作用の開始の両方が含まれます。
前に順序付けされるのは、単一のスレッドによって実行される評価間の非対称で推移的なペアワイズ関係であり、これらの評価間に半順序が生じます。任意の 2 つの評価 A と B が与えられた場合、A が B の前にシーケンスされている場合、A の実行は B の実行よりも優先されます。 ...
式 A と B の評価の間にシーケンス ポイントが存在することは、A に関連付けられたすべての値の計算と副作用が、B に関連付けられたすべての値の計算と副作用の前に順序付けられることを意味します。
/--/
実際の実装では、式の値が使用されておらず、必要な副作用 (関数の呼び出しまたは揮発性オブジェクトへのアクセスによるものを含む) が生成されていないと推測できる場合、式の一部を評価する必要はありません。
/--/
準拠する実装の最小要件は次のとおりです。
— volatile オブジェクトへのアクセスは、抽象マシンのルールに従って厳密に評価されます。
これは正確に解釈するのは簡単ではありませんが、平易な英語で大まかに言うと、次のようになります。命令キャッシュ/分岐予測を使用して CPU でパフォーマンスを向上させるために、または CPU レジスタに特定の値が便利に格納されているためにパフォーマンスを向上させるために、別の順序で実行する可能性があります。
(C11 標準では、volatile オブジェクトがスレッドセーフであることが保証されていないことも明示的に述べられています。コンテキスト切り替え後の volatile オブジェクトの値は未規定の動作です。)
例を編集する
コードを考えると
volatile int x;
volatile int y;
volatile int z;
x=i;
y=something;
z=i;
その場合、コンパイラは実行可能ファイル内の命令を次の順序に並べ替えることができません。
x=i;
z=i;
y=something
y へのアクセスは、z へのアクセスの前に順序付けする必要があるためです。セミコロンにシーケンス ポイントがあります。しかし、変数が volatile でなかった場合、プログラムの結果に影響を与えないと判断できれば、コンパイラがそれらを並べ替えても問題ありません。
この意図的に考案された例を考えてみましょう:
volatile int v = 5;
int x;
int y = (x=7), (x+v);
コンマがシーケンス ポイントを作成することを思い出してください。したがって、課題はが評価さx=7
れる前に完了します。x+v
さらに、v
isであるため、宣言とアクセス ポイントの間にコード変更がないという理由だけvolatile
で、コンパイラはそれを想定しない場合があります。コンパイラは、 readへの命令を生成する必要があります。5
v
v
ただし、これは重要な決定をコンパイラに委ねます:正確にいつv
読み取る必要があるか? より具体的には、割り当てる前に読んでも大丈夫でしょうv
x
か?
これは、あなたの質問からの声明が出てくるところです:
コンパイラは、揮発性変数へのアクセスをシーケンス ポイント間で移動できない場合があります
v
を割り当てる前にコンパイラが読み取ることを明示的に禁止しx
ます。これを行うと、コンマ演算子によって作成されたシーケンス ポイントを越えてアクセスが移動するためです。この制限がなければ、コンパイラは をv
代入する前でも後でも自由に読み取ることができたでしょうx
。
C++ の「抽象マシン」のコンテキストでは、プログラムはvolatile
実行される一連のアクセス (「観察可能な動作」) によって定義され、それ以外はオプティマイザーによって変更される可能性があります。
たとえば、多くのレジスタを備えた CPU を使用している場合、宣言されていない限り、コンパイラがそれらにオブジェクトを格納することは完全に許容されvolatile
ます。CPU によって実行されるメモリ アクセスを見ている人は、プログラムが実行するすべてのことを理解することはできなくなりますが、volatile
アクセスが存在することは保証されます。
volatile
最適化されていないプログラムと同じデータで同じシーケンスのアクセスが生成される場合、プログラムは正しく最適化されています。
シーケンス ポイントの概念は別のレイヤーに存在します。これは、抽象マシン内の操作の順序付けに関係しています。間にシーケンス ポイントがない 2 つの操作がある場合、順序の保証はありx = 0; return x++ + x++;
ません。これが、定義された結果を持たない理由です。
volatile
非アクセスについてほとんど保証されていないため、これら 2 つの概念を組み合わせるのは困難です。私がすぐに思いつく唯一の例は
int *y = ...;
volatile int *x = ...;
std::exception up;
if (*y == 0)
throw up;
return *x;
*y
ではないため、volatile
アクセスをどこにでも移動し、可能であれば最適化することも完全に許容されますが*x
、*y == 0
.
揮発性とは、変数がプログラムの外部で変更される可能性があり、コンパイラがプログラムの式シーケンス (命令) で変数を移動することによってそのアクセスを最適化できないことを意味します。揮発性の使用法の良い例は、OS ティックです。volatile.