Namespaces
Variants

volatile type qualifier

From cppreference.net

C语言 类型系统 中的每个独立类型都有若干该类型的 限定 版本,对应 const volatile 以及(对于对象指针类型) restrict 限定符中的一种、两种或全部三种组合。本页描述 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 和 vs.ci 的类型分别是 volatile int 和 const volatile int

如果数组类型通过使用 typedef 声明带有 volatile 类型限定符,则数组类型本身不具有 volatile 限定,但其元素类型具有 volatile 限定。

(C23 前)

数组类型与其元素类型始终被视为具有相同的 volatile 限定。

(C23 起)
typedef int A[2][3];
volatile A a = {{4, 5, 6}, {7, 8, 9}}; // volatile int 的数组的数组
int* pi = a[0]; // 错误:a[0] 具有 volatile int* 类型
void *unqual_ptr = a; // C23 前有效;C23 起错误
// 注意:clang 即使在 C89-C17 模式下也应用 C++/C23 的规则

如果函数类型被声明为带有 volatile 类型限定符(通过使用 typedef ),其行为是未定义的。

在函数声明中,关键字 volatile 可以出现在用于声明函数参数数组类型的方括号内。它限定数组类型转换后的指针类型。

以下两个声明声明了相同的函数:

void f(double x[volatile], const double y[volatile]);
void f(double * volatile x, const double * volatile y);
(since C99)

指向非 volatile 类型的指针可以隐式转换为指向相同或 兼容类型 的 volatile 限定版本的指针。反向转换需要显式类型转换。

int* p = 0;
volatile int* vp = p; // 正确:添加限定符(int 到 volatile int)
p = vp; // 错误:丢弃限定符(volatile int 到 int)
p = (int*)vp; // 正确:强制类型转换

注意指向 T 的指针不能转换为指向 volatile T 的指针;要使两种类型兼容,它们的限定符必须完全相同:

char *p = 0;
volatile char **vpp = &p; // 错误:char* 与 volatile char* 是不兼容的类型
char * volatile *pvp = &p; // 正确,添加限定符(从 char* 到 char*volatile)

目录

volatile的用途

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 is an lvalue of type volatile short
2) static volatile 类型为 sig_atomic_t 的对象用于与 signal 处理程序进行通信。
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; // reads from and writes to a non-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; // reads from a volatile
            vd += prod; // reads from and writes to a 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

参考文献

  • C17 标准 (ISO/IEC 9899:2018):
  • 6.7.3 类型限定符 (p: 87-90)
  • C11 标准 (ISO/IEC 9899:2011):
  • 6.7.3 类型限定符 (p: 121-123)
  • C99标准(ISO/IEC 9899:1999):
  • 6.7.3 类型限定符(第108-110页)
  • C89/C90 标准 (ISO/IEC 9899:1990):
  • 6.5.3 类型限定符

参见

C++ 文档 关于 cv( const volatile )类型限定符