volatile 的语义

Semantics of volatile

为了这个问题,让我们只看对 volatile 变量的读取。我读过的所有讨论,唯一的结论是,对声明为 volatile 的同一个变量的多次读取不能优化为单一效果。

但我认为这有点严格。考虑对一个变量的两次读取,它们之间没有任何副作用,或者它们之间没有读取任何其他 volatile 变量。

现在我们知道 volatile 变量中的值可以随时更改(编译器没有任何提示)。但是程序员没有办法保证两次读取之间会发生变化。这意味着两个读取看到相同的值是程序的有效行为。

所以编译器不能强制执行此行为吗?进行一次读取并使用该值两次。

例如

int foo(volatile int * x) {
    return *x + *x;
}

在这种情况下,编译器可以进行单次读取吗?

我希望我的查询是清楚的。

我还假设一个读取本身没有副作用的系统(比如计数器的增量,或者值随每次读取而变化)。或者是否存在这样的系统?

我查看了从 gccclang 生成的程序集,即使进行了最大优化,它们也会插入两个读取。我的问题是他们是否过于保守?

编辑:为了不使我的问题复杂化并避免与实现定义的子表达式求值顺序混淆,我们可以看一下示例 -

int foo(volatile int * x) {
    int a = *x;
    int b = *y;
    return a + b;
}

但我也保留了前面的例子,因为一些答案和评论已经引用了那个。

int foo(volatile int * x) { return *x + *x; }

必须生成两个读数。无法优化易失性访问。他们就像 IO。

6.7.3p7:

7 An object that has volatile-qualified type may be modified in ways unknown to the implementation or have other unknown side effects. Therefore any expression referring to such an object shall be evaluated strictly according to the rules of the abstract machine, as described in 5.1.2.3. Furthermore, at every sequence point the value last stored in the object shall agree with that prescribed by the abstract machine, except as modified by the unknown factors mentioned previously.134) What constitutes an access to an object that has volatile-qualified type is implementation-defined.


当然,您可以允许通过引入一个临时文件来只读取一次:

int foo(volatile int * x) { int x_cp = *x; return x_cp + x_cp; }

(根据 generated assembly 判断,gcc 接受优化级别 -O1 或更高级别的提示。)

从内存位置读取可能会产生副作用。当然,该程序必须使用的不仅仅是标准 C。读取只能在依赖于实现定义行为的程序中产生副作用。

一个常见的例子是读取 memory-mapped peripheral。在许多体系结构中,当数据被读取或写入特定范围的内存位置时,主处理器会与外围设备交换数据。如果内存位置映射到外围设备,则执行两次读取会向外围设备发送两个读取请求。外设可能对每次读取执行非幂等操作。

例如,每次从串行通信外设读取一个字节都会传输外设输入队列中的下一个字节。因此,如果使用该串行外围设备的字节读取寄存器的地址调用 foo,则它会从外围设备的读取缓冲区中提取两个连续的字节。不允许编译器将行为更改为只读一个字节。

嗯,除了行为是未定义的,因为读取之间没有序列点,并且从 volatile 读取是一个副作用。正确的函数是

int foo2(volatile int *x) {
    int x1 = *x;
    int x2 = *x;
    return x1 + x2;
}

我希望大多数编译器为 foo 生成与 foo2 相同的代码。

Now we know that the value in a volatile variable can change any time (without the compiler having a hint of it). But there is no way for the programmer to ensure that the change will happen between the two reads. This means that both the reads seeing the same value is a valid behavior for the program.

你由此得出错误的结论。 值可能不会改变。但你不知道。而编译器不知道。

如果编译器不知道为什么要允许编译器假设任何更改? 因此很明确:NO!编译器不得在此处组合任何读取访问权限。

这是一个奇怪的假设。 如果你不能确保所有的兔子都是白色的,怎么能假设所有的兔子都是黑色的呢?

也可能是第一次读取本身导致值发生变化。

如果您查看某些硬件,单独进行读取访问可能至关重要。 某些定时器或中断控制器在读取时会清除某些位。

UART 或以太网控制器也可以通过一个地址提供整个接收缓冲区。您必须从同一个地址多次读取。

volatile关键字是防止编译器作弊的手段。