キーワードは何をしvolatile
ますか?C++ では、どのような問題が解決されますか?
私の場合、故意にそれを必要としたことは一度もありません。
volatile
たとえば、完全に別のプロセス/デバイス/書き込み先のメモリ内のスポットから読み取る場合に必要です。
私はストレート C のマルチプロセッサ システムでデュアル ポート RAM を使用していました。ハードウェアで管理された 16 ビット値をセマフォとして使用して、相手がいつ作業を終了したかを把握していました。基本的に、次のようにしました。
void waitForSemaphore()
{
volatile uint16_t* semPtr = WELL_KNOWN_SEM_ADDR;/*well known address to my semaphore*/
while ((*semPtr) != IS_OK_FOR_ME_TO_PROCEED);
}
がないvolatile
と、オプティマイザはループを役に立たないと見なし (男は決して値を設定しません! 彼は気が狂っています。そのコードを削除してください!)、私のコードはセマフォを取得せずに続行し、後で問題を引き起こします。
volatile
メモリマップされたハードウェアデバイスを読み書きする必要がある組み込みシステムまたはデバイスドライバーを開発するときに必要です。特定のデバイス レジスタの内容はいつでも変更される可能性があるvolatile
ため、そのようなアクセスがコンパイラによって最適化されないようにするためのキーワードが必要です。
一部のプロセッサには、64 ビットを超える精度を持つ浮動小数点レジスタがあります (たとえば、SSE を使用しない 32 ビット x86、Peter のコメントを参照)。こうすることで、倍精度数に対して複数の演算を実行すると、各中間結果を 64 ビットに切り捨てる場合よりも実際に高い精度の答えが得られます。
これは通常は素晴らしいことですが、コンパイラがどのようにレジスタを割り当てて最適化を行ったかによって、まったく同じ入力に対するまったく同じ操作に対して異なる結果が得られることを意味します。一貫性が必要な場合は、volatile キーワードを使用して、各操作を強制的にメモリに戻すことができます。
また、Kahan の合計など、代数的な意味はないが浮動小数点エラーを減らすアルゴリズムにも役立ちます。代数的には nop であるため、一部の中間変数が揮発性でない限り、誤って最適化されることがよくあります。
Dan Saks による「Volatile as a promise」の記事から:
(...) 揮発性オブジェクトは、値が自発的に変化する可能性があるオブジェクトです。つまり、オブジェクトを volatile として宣言すると、プログラム内のステートメントがオブジェクトを変更するように見えなくても、オブジェクトの状態が変更される可能性があることをコンパイラーに伝えることになります。」
volatile
このキーワードに関する彼の 3 つの記事へのリンクを次に示します。
ロックフリーデータ構造を実装する場合は、volatileを使用する必要があります。それ以外の場合、コンパイラーは変数へのアクセスを自由に最適化でき、セマンティクスが変更されます。
言い換えると、volatileは、この変数へのアクセスが物理メモリの読み取り/書き込み操作に対応している必要があることをコンパイラに通知します。
たとえば、これはWin32APIでInterlockedIncrementが宣言される方法です。
LONG __cdecl InterlockedIncrement(
__inout LONG volatile *Addend
);
標準 C では、使用する場所の 1 つにvolatile
シグナル ハンドラーがあります。実際、標準 C では、シグナル ハンドラで安全にできることは、volatile sig_atomic_t
変数を変更するか、すぐに終了することだけです。確かに、知る限り、volatile
未定義の動作を回避するために を使用する必要がある標準 C の唯一の場所です。
ISO/IEC 9899:2011 §7.14.1.1
signal
関数
abort
¶5 or関数の呼び出しの結果以外でシグナルが発生した場合raise
、値を割り当てる以外の方法で、ロックフリーのアトミック オブジェクトではない、静的またはスレッド ストレージ期間を持つオブジェクトをシグナル ハンドラーが参照する場合、動作は未定義です。として宣言されたオブジェクトにvolatile sig_atomic_t
、またはシグナルハンドラが標準ライブラリ内のabort
関数、_Exit
関数、quick_exit
関数、またはsignal
呼び出しの原因となったシグナルに対応するシグナル番号に等しい最初の引数を持つ関数以外の関数を呼び出します。ハンドラ。さらに、このようなsignal
関数呼び出しの結果として SIG_ERR が返された場合、 の値errno
は不確定です。252)252)非同期シグナルハンドラによってシグナルが生成された場合、動作は未定義です。
つまり、標準 C では、次のように記述できます。
static volatile sig_atomic_t sig_num = 0;
static void sig_handler(int signum)
{
signal(signum, sig_handler);
sig_num = signum;
}
他にはあまりありません。
POSIX は、シグナル ハンドラーでできることについてはるかに寛大ですが、まだ制限があります (制限の 1 つは、標準 I/O ライブラリなどをprintf()
安全に使用できないことです)。
私が 1990 年代初頭に取り組んでいた大規模なアプリケーションには、setjmp と longjmp を使用した C ベースの例外処理が含まれていました。volatile キーワードは、変数がレジスタに格納されて longjmp によって消去されないように、「catch」句として機能するコード ブロックに値を保持する必要がある変数に必要でした。
コンパイラがコードをステップスルーするときに表示できるようにしたい変数を最適化することを要求するときに、デバッグビルドでこれを使用しました。
意図したとおりに使用する以外に、volatile は (テンプレート) メタプログラミングで使用されます。volatile 属性 (const など) がオーバーロードの解決に関与するため、偶発的なオーバーロードを防ぐために使用できます。
template <typename T>
class Foo {
std::enable_if_t<sizeof(T)==4, void> f(T& t)
{ std::cout << 1 << t; }
void f(T volatile& t)
{ std::cout << 2 << const_cast<T&>(t); }
void bar() { T t; f(t); }
};
これは合法です。どちらのオーバーロードも潜在的に呼び出し可能であり、ほとんど同じことを行います。volatile
いずれにしても bar が非 volatile を渡さないことがわかっているため、オーバーロードのキャストは合法T
です。ただし、volatile
バージョンは厳密に悪いため、不揮発性f
が利用可能な場合は過負荷解決で選択されることはありません。
volatile
コードが実際にメモリ アクセスに依存することはないことに注意してください。
組み込み用に開発していて、割り込みハンドラーで変更できる変数をチェックするループがあります。「揮発性」がないと、ループはヌープになります-コンパイラーが知る限り、変数は決して変更されないため、チェックを最適化します。
より伝統的な環境の別のスレッドで変更される可能性のある変数にも同じことが当てはまりますが、同期呼び出しを頻繁に行うため、コンパイラは最適化でそれほど自由ではありません。
このvolatile
キーワードは、コンパイラーが判断できない方法で変更される可能性があるオブジェクトに対して、コンパイラーが最適化を適用しないようにすることを目的としています。
as として宣言されたオブジェクトはvolatile
、その値が現在のコードのスコープ外のコードによっていつでも変更される可能性があるため、最適化から除外されます。volatile
システムは、前の命令が同じオブジェクトからの値を要求した場合でも、要求された時点でオブジェクトの値を一時レジスターに保持するのではなく、常にメモリー位置からオブジェクトの現在の値を読み取ります。
次のケースを検討してください
1) スコープ外の割り込みサービス ルーチンによって変更されたグローバル変数。
2) マルチスレッド アプリケーション内のグローバル変数。
volatile 修飾子を使用しない場合、次の問題が発生する可能性があります
1) 最適化がオンになっていると、コードが期待どおりに機能しない場合があります。
2) 割り込みを有効にして使用すると、コードが期待どおりに動作しない場合があります。
https://en.wikipedia.org/wiki/Volatile_(computer_programming)
volatileキーワードは、一部の変数(スレッドまたは割り込みルーチンによって変更可能)へのアクセスを最適化しないようにコンパイラーに指示するために使用されるという事実に加えて、コンパイラーのバグを削除するためにも使用できます-はい、できます--- 。
たとえば、私は組み込みプラットフォームで作業しましたが、コンパイラーが変数の値に関していくつかの間違った推測をしていました。コードが最適化されていない場合、プログラムは正常に実行されます。最適化(これは重要なルーチンであったために本当に必要でした)では、コードは正しく機能しませんでした。唯一の解決策(あまり正しくはありませんが)は、「faulty」変数を揮発性として宣言することでした。
シグナル ハンドラー関数で、グローバル変数にアクセス/変更する (たとえば、exit = true としてマークする) 場合は、その変数を 'volatile' として宣言する必要があります。