volatile
キーワードは何をしますか? C++では、どのような問題を解決しますか?
私の場合、意図的にそれを必要としたことはありません。
volatile
は、たとえば完全に独立したプロセス/デバイス/書き込み先など、メモリ内のスポットから読み取る場合に必要です。
以前はストレートCのマルチプロセッサシステムでデュアルポートラムを使用していました。セマフォとしてハードウェア管理の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オブジェクトは、値が自然に変化する可能性があるオブジェクトです。つまり、オブジェクトをvolatileとして宣言すると、プログラム内のどのステートメントもオブジェクトを変更しないように見えても、オブジェクトの状態が変わる可能性があることをコンパイラに伝えています。」
volatile
キーワードに関する彼の3つの記事へのリンクは次のとおりです。
ロックフリーデータ構造を実装する場合は、volatileを使用する必要があります。それ以外の場合、コンパイラは変数へのアクセスを自由に最適化し、セマンティクスを変更します。
別の言い方をすれば、volatileは、この変数へのアクセスが物理メモリの読み取り/書き込み操作に対応する必要があることをコンパイラーに伝えます。
たとえば、Win32 APIでInterlockedIncrementを宣言する方法は次のとおりです。
LONG __cdecl InterlockedIncrement(
__inout LONG volatile *Addend
);
1990年代初頭に作業していた大規模なアプリケーションには、setjmpとlongjmpを使用したCベースの例外処理が含まれていました。 volatileキーワードは、 "catch"句として機能するコードブロックに値を保存する必要がある変数に必要でした。これらのvarがレジスタに格納され、longjmpによって消去されることはありません。
標準Cでは、volatile
を使用する場所の1つにシグナルハンドラがあります。実際、標準Cでは、volatile sig_atomic_t
変数を変更するか、すぐに終了するだけで、シグナルハンドラで安全に実行できます。実際、私の知る限り、未定義の動作を避けるためにvolatile
を使用する必要があるのは標準Cの唯一の場所です。
ISO/IEC 9899:2011§7.14.1.1
signal
関数¶5
abort
または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()
など)を安全に使用できないことです。 )。
意図したとおりに使用するほかに、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が不揮発性T
を渡さないことを知っているので、合法です。ただし、volatile
バージョンは厳密に悪いため、不揮発性f
が使用可能な場合は、オーバーロード解決で決して選択しないでください。
コードは実際にはvolatile
メモリアクセスに依存しないことに注意してください。
コンパイラがコードをステップ実行するときに表示できるようにしたい変数を最適化することをコンパイラが要求するときに、デバッグビルドで使用しました。
組み込み向けの開発では、割り込みハンドラーで変更できる変数をチェックするループがあります。 「揮発性」がないと、ループは無操作になります。コンパイラーが知る限り、変数は変更されないため、チェックアウェイが最適化されます。
同じことが、より伝統的な環境の異なるスレッドで変更される可能性のある変数にも当てはまりますが、そこでは同期呼び出しを頻繁に行うため、コンパイラは最適化に関してそれほど自由ではありません。
volatile
キーワードは、コンパイラが決定できない方法で変化する可能性のあるオブジェクトに最適化を適用しないようにすることを目的としています。
volatile
として宣言されたオブジェクトは、現在のコードのスコープ外のコードによっていつでも値を変更できるため、最適化から除外されます。システムは、以前の命令が同じオブジェクトから値を要求した場合でも、要求された時点で一時レジスタに値を保持するのではなく、常にメモリ位置からvolatile
オブジェクトの現在の値を読み取ります。
次の場合を考慮してください
1)スコープ外の割り込みサービスルーチンによって変更されたグローバル変数。
2)マルチスレッドアプリケーション内のグローバル変数。
volatile修飾子を使用しない場合、次の問題が発生する可能性があります
1)最適化がオンになっていると、コードが期待どおりに機能しない場合があります。
2)割り込みが有効で使用されている場合、コードが期待どおりに機能しない場合があります。
https://en.wikipedia.org/wiki/Volatile_(computer_programming)
あなたのプログラムはvolatile
キーワードがなくても動作するようですか?おそらくこれが理由です:
前述のように、volatile
キーワードは次のような場合に役立ちます
volatile int* p = ...; // point to some memory
while( *p!=0 ) {} // loop until the memory becomes zero
しかし、外部関数または非インライン関数が呼び出されると、ほとんど効果がないようです。例えば。:
while( *p!=0 ) { g(); }
volatile
の有無にかかわらず、ほぼ同じ結果が生成されます。
g()を完全にインライン化できる限り、コンパイラは進行中のすべてを見ることができるため、最適化できます。ただし、プログラムがコンパイラが見えない場所を呼び出した場合コンパイラが前提条件を作成することは安全ではないため、コンパイラは常にメモリから直接読み取るコードを生成します。
ただし、関数g()が明示的な変更またはコンパイラ/リンカーの巧妙性のために)インラインになると、volatile
キーワード!
したがって、プログラムが機能しないように見える場合でも、volatile
キーワードを追加することをお勧めします。これにより、将来の変更に関して意図が明確になり、堅牢になります。
Cの初期の頃、コンパイラは左辺値を読み書きするすべてのアクションをメモリ操作として解釈し、コードに現れる読み書きと同じ順序で実行されるようになりました。多くの場合、コンパイラーに操作を並べ替えて統合するためのある程度の自由が与えられていれば、効率は大幅に向上する可能性がありましたが、これには問題がありました。 someの順序で指定する必要があるという理由だけで、操作でさえ特定の順序で指定されることがよくあり、したがってプログラマーは多くの同等に優れた代替案の1つを選択しましたが、常にそうではありませんでした。特定の操作が特定の順序で発生することが重要な場合があります。
どのシーケンスの詳細が重要であるかは、ターゲットプラットフォームとアプリケーション分野によって異なります。規格は特に詳細な制御を提供するのではなく、単純なモデルを選択しました。修飾されていないvolatile
の左辺値で一連のアクセスが行われた場合、コンパイラは適切と思われる順序で並べ替えて統合する場合があります。アクションがvolatile
で修飾された左辺値で実行される場合、品質の実装は、非標準構文の使用を必要とせずに、目的のプラットフォームおよびアプリケーションフィールドをターゲットとするコードに必要な追加の順序保証を提供する必要があります。
残念ながら、プログラマが必要とする保証を特定するのではなく、多くのコンパイラは代わりに、標準で義務付けられている最低限の保証を提供することを選択しました。これにより、volatile
の有用性が本来よりもはるかに低くなります。たとえば、gccまたはclangでは、基本的な「ハンドオフミューテックス」[ミューテックスを取得およびリリースしたタスクが、他のタスクが完了するまでそうしない]を実装する必要があるプログラマは、1つを実行する必要があります次の4つのこと:
コンパイラがインライン化できず、プログラム全体の最適化を適用できない関数にミューテックスの取得と解放を行います。
ミューテックスによって保護されているすべてのオブジェクトをvolatile
として修飾します。ミューテックスを取得してから解放する前にすべてのアクセスが発生する場合は必要ないものです。
最適化レベル0を使用して、修飾されていないregister
のすべてのオブジェクトがvolatile
であるかのようにコンパイラーにコードを生成させます。
Gcc固有のディレクティブを使用します。
対照的に、iccなどのシステムプログラミングに適した高品質のコンパイラを使用する場合、別のオプションがあります。
volatile
修飾の書き込みが実行されることを確認してください。基本的な「ハンドオフミューテックス」を取得するには、volatile
読み取り(準備ができているかどうかを確認する)が必要です。また、volatile
書き込みも必要ではありません(反対側はしようとしません)返されるまで再取得します)が、無意味なvolatile
書き込みを実行する必要があることは、gccまたはclangで使用可能なオプションよりも優れています。
Volatileキーワードは、ある変数(スレッドまたは割り込みルーチンで変更可能)へのアクセスを最適化しないようコンパイラーに指示するために使用されるという事実に加えて、いくつかのコンパイラーのバグを除去するために使用される-はい---。
たとえば、コンパイラが変数の値に関して間違った仮定をしていた組み込みプラットフォームで作業していました。コードが最適化されていない場合、プログラムは正常に実行されます。最適化(これは重要なルーチンだったために本当に必要でした)では、コードは正しく機能しませんでした。唯一の解決策は(あまり正確ではありませんが)、「faulty」変数をvolatileとして宣言することでした。
シグナルハンドラー関数で、グローバル変数にアクセス/変更する場合(たとえば、exit = trueとしてマークする)、その変数を「volatile」として宣言する必要があることを思い出してください。