volatile 型修飾子
C の型システムでは、個々の型には、それぞれ const 修飾子、 volatile 修飾子、 restrict 修飾子 (オブジェクトへのポインタ型の場合のみ) のうちの1つ、2つ、または3つ全部の組み合わせに対応する、その型の修飾されたバージョンがあります。 このページではそのうちの volatile 修飾子について説明します。
volatile 修飾された型の左辺値式を通して行われたすべてのアクセス (読み書き両方) は、最適化の目的に対しては観測可能な副作用とみなされ、抽象機械のルールに厳密に従って評価されます (つまり、すべての書き込みは次の副作用完了点より前のどこかの時点で完了します)。 これは、単一の実行のスレッド内では、 volatile アクセスを最適化によって除去することや、副作用完了点によって分離されている他の可視な副作用との相対的な順序を変更することが、できないことを意味します。
非 volatile な値を volatile な型へキャストすることは効果がありません。 非 volatile なオブジェクトを volatile な意味論でアクセスするためには、そのアドレスを取り、それを volatile へのポインタにキャストし、そのポインタを通してアクセスしなければなりません。
非 volatile な左辺値を通して volatile 修飾された型のオブジェクトを読み書きしようとするあらゆる試みは未定義動作です。
volatile int n = 1; // volatile 修飾された型のオブジェクト。 int* p = (int*)&n; int val = *p; // 未定義動作。
volatile 修飾された構造体型または共用体型のメンバには、その属する型の修飾が付与されます (.
演算子でアクセスしたときと ->
演算子でアクセスしたとき、どちらも)。
struct s { int i; const int ci; } s; // s.i の型は int で、 s.ci の型は const int です。 volatile struct s vs; // vs.i の型は volatile int で、 vs.ci の型は const volatile int です。
配列型が (typedef を用いて) volatile 型修飾子付きで宣言された場合は、その配列型ではなく、その要素型が volatile 修飾されます。 関数型が (typedef を用いて) volatile 型修飾子付きで宣言された場合は、動作は未定義です。
typedef int A[2][3]; volatile A a = {{4, 5, 6}, {7, 8, 9}}; // volatile int の配列の配列。 int* pi = a[0]; // エラー、 a[0] の型は volatile int* です。
関数宣言において、配列型の関数引数を宣言するための角括弧内で、キーワード 以下の2つの宣言は同じです。 void f(double x[volatile], const double y[volatile]); void f(double * volatile x, const double * volatile y); |
(C99以上) |
非 volatile 型へのポインタは、同じまたは互換な型の volatile 修飾されたバージョンへのポインタに、暗黙に変換できます。 逆変換はキャスト式を用いて行えます。
int* p = 0; volatile int* vp = p; // OK、修飾子を追加します (int → volatile int)。 p = vp; // エラー、修飾子を除去 (volatile int → int) することはできません。 p = (int*)vp; // OK、キャストを使用すれば修飾子を除去できます。
T
へのポインタへのポインタは、 volatile T
へのポインタへのポインタと、互換でないことに注意してください。 2つのポインタが互換であるためには、その修飾子が同じでなければなりません。
char *p = 0; volatile char **vpp = &p; // エラー、 char* と volatile char* は互換な型ではありません。 char * volatile *pvp = &p; // OK、修飾子を追加します (char* → char*volatile)。
目次 |
[編集] voltile の用途
volatile
オブジェクトはメモリマップド I/O ポートをモデル化し、 static
const
volatile
オブジェクトはメモリマップド入力ポートをモデル化します (例えばリアルタイムクロックなど)。
volatile short *ttyport = (volatile short*)TTYPORT_ADDR; for(int i = 0; i < N; ++i) *ttyport = a[i]; // *ttyport は volatile short 型の左辺値です。
volatile 変数はスレッド間のやりとりには適していないことに注意してください。 アトミック性や同期、メモリの順序付けは提供されません。 同期せずに別のスレッドから変更される volatile 変数からの読み込みや、同期されていない複数のスレッドからの並行的な変更は、データ競合による未定義動作です。
[編集] キーワード
[編集] 例
最適化を無効化する volatile の使用方法をデモンストレーションします。
#include <stdio.h> #include <time.h> int main(void) { clock_t t = clock(); double d = 0.0; for (int n = 0; n < 10000; ++n) for (int m = 0; m < 10000; ++m) d += d * n * m; // 非 volatile への読み書き printf("Modified a non-volatile variable 100m times. " "Time used: %.2f seconds\n", (double)(clock() - t)/CLOCKS_PER_SEC); t = clock(); volatile double vd = 0.0; for (int n = 0; n < 10000; ++n) for (int m = 0; m < 10000; ++m) { double prod = vd * n * m; // volatile への読み vd += prod; // volatile への読み書き } printf("Modified a volatile variable 100m times. " "Time used: %.2f seconds\n", (double)(clock() - t)/CLOCKS_PER_SEC); }
出力例:
Modified a non-volatile variable 100m times. Time used: 0.00 seconds Modified a volatile variable 100m times. Time used: 0.79 seconds