名前空間
変種
操作

volatile 型修飾子

提供: cppreference.com
< c‎ | language

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* です。

関数宣言において、配列型の関数引数を宣言するための角括弧内で、キーワード volatile を使用できます。 これは、その配列型の変換後のポインタ型を修飾します。

以下の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 の用途

1) static 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 型の左辺値です。
2) sig_atomic_t 型の static volatile オブジェクトは、シグナルハンドラとやりとりするために使用されます。
3) setjmp マクロの呼び出しを含む関数にローカルな volatile 変数は、 longjmp から戻った後にも値を保持することが保証されます。
4) また、 volatile 変数は、特定の形式の最適化を無効化するために、例えば、マイクロベンチマークのためにデッドストアの除去や定数の畳み込みを無効化するために、使用できます。

volatile 変数はスレッド間のやりとりには適していないことに注意してください。 アトミック性や同期、メモリの順序付けは提供されません。 同期せずに別のスレッドから変更される 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

[編集] 参考文献

  • C11 standard (ISO/IEC 9899:2011):
  • 6.7.3 Type qualifiers (p: 121-123)
  • C99 standard (ISO/IEC 9899:1999):
  • 6.7.3 Type qualifiers (p: 108-110)
  • C89/C90 standard (ISO/IEC 9899:1990):
  • 3.5.3 Type qualifiers

[編集] 関連項目

cv (const および volatile) 型修飾子C++リファレンス