命名空间
变体
操作

易变类型限定符

来自 cppreference.com
< c‎ | 语言

C 类型系统 中的每个单独类型都有该类型的几种限定版本,对应于一个、两个或所有三个 constvolatile 以及针对指向对象类型的指针,restrict 限定符。本页面描述了volatile 限定符的影响。

对易变限定类型左值表达式进行的每次访问(读写均包括)都被视为优化目的的可见副作用,并严格按照抽象机器的规则进行评估(即,所有写入在下一个序列点之前完成)。这意味着,在单个执行线程内,不能优化掉易变访问,也不能相对于另一个可见副作用重新排序,后者与易变访问之间由 序列点 分隔。

将非易变值强制转换为易变类型没有影响。要使用易变语义访问非易变对象,必须将其地址强制转换为指向易变类型的指针,然后通过该指针进行访问。

任何尝试通过非易变左值读取或写入类型为易变限定的对象都会导致未定义行为

volatile int n = 1; // object of volatile-qualified type
int* p = (int*)&n;
int val = *p; // undefined behavior

易变限定结构体或联合体类型成员将获得其所属类型的限定(无论是使用 . 运算符还是 -> 运算符进行访问)。

struct s { int i; const int ci; } s;
// the type of s.i is int, the type of s.ci is const int
volatile struct s vs;
// the types of vs.i and vs.ci are volatile int and const volatile int

如果数组类型使用易变类型限定符进行声明(通过使用 typedef),则数组类型不会被易变限定,但其元素类型会被限定。

(直到 C23)

数组类型及其元素类型始终被视为相同易变限定。

(自 C23 起)
typedef int A[2][3];
volatile A a = {{4, 5, 6}, {7, 8, 9}}; // array of array of volatile int
int* pi = a[0]; // Error: a[0] has type volatile int*
void *unqual_ptr = a; // OK until C23; error since C23
// Notes: clang applies the rule in C++/C23 even in C89-C17 modes

如果函数类型使用易变类型限定符进行声明(通过使用 typedef),则行为未定义。

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

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

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

指向非易变类型的指针可以隐式转换为指向相同或 兼容 类型的易变限定版本的指针。反向转换需要强制转换表达式。

int* p = 0;
volatile int* vp = p; // OK: adds qualifiers (int to volatile int)
p = vp; // Error: discards qualifiers (volatile int to int)
p = (int*)vp; // OK: cast

注意,指向指向 T 的指针不是可以转换为指向指向 volatile T 的指针的;对于两个类型要兼容,它们的限定必须相同

char *p = 0;
volatile char **vpp = &p; // Error: char* and volatile char* are not compatible types
char * volatile *pvp = &p; // OK, adds qualifiers (char* to char*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 用于与 信号 处理程序进行通信。
3) volatile 变量是函数局部变量,该函数包含对 setjmp 宏的调用,它们是 longjmp 返回后保证保留其值的唯一局部变量。
4) 此外,易变变量可以用于禁用某些形式的优化,例如禁用针对微基准测试的死存储消除或常量折叠。

注意,易变变量不适合在线程之间进行通信;它们不提供原子性、同步或内存排序。由于数据竞争,对易变变量的读取(该变量被另一个线程修改,但没有同步或两个非同步线程同时修改)是未定义的行为。

[编辑] 关键字

易变

[编辑] 示例

演示了使用易变变量来禁用优化的示例

#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 类型限定符 (p: 108-110)
  • C89/C90 标准 (ISO/IEC 9899:1990)
  • 6.5.3 类型限定符

[编辑] 另请参阅

C++ 文档 针对 cv (constvolatile) 类型限定符