5

总的来说,我似乎有一个合理的理解volatiles,但是有一个看似晦涩的案例,我不确定按照标准应该如何工作。我已经阅读了 C99 的相关部分以及关于 SO 的十几篇或更多相关帖子,但找不到此案例的逻辑或解释此案例的地方。

假设我们有这段代码:

  int a, c;
  volatile int b;
  a = b = 1;
  c = b += 1; /* or equivalently c = ++b; */

应该a这样评价:

  b = 1;
  a = b; // volatile is read

或像这样:

  b = 1;
  a = 1; // volatile isn't read

?

同样,应该c这样评估:

  int tmp = b;
  tmp++;
  b = tmp;
  c = b; // volatile is read

或像这样:

  int tmp = b;
  tmp++;
  b = tmp;
  c = tmp; // volatile isn't read

?

在简单的情况下,a = b; c = b;事情很清楚。但是上面的那些呢?

基本上,问题是,当对象为 volatile 时,C99 的 6.5.16c3 中的“表达式在赋值后具有左操作数的值”到底是什么意思?:

赋值运算符将值存储在左操作数指定的对象中。赋值表达式在赋值之后具有左操作数的值,但不是左值。

它是否意味着额外读取 volatile 以产生赋值表达式的值?

更新

所以,这就是两难境地。

如果“赋值后对象的值”不是从对 volatile 对象的额外读取中获得的,那么编译器会假设 volatile 对象b

  • 能够保存写入其中的任意int值,它可能不是(例如,位 0 硬连线为 0,这对于硬件寄存器来说并不罕见,我们应该使用 volatiles)
  • 在分配写入发生的点和获得表达式值的点之间不能改变(这可能是硬件寄存器的问题)

正因为如此,如果不是从 volatile 对象的额外读取中获得表达式值,则不会产生 volatile 对象的值,标准声称应该是这种情况。

这两个假设似乎都不太适合易失性对象的性质。

如果,OTOH,“赋值后对象的值”是从对所述易失性对象的额外隐含读取中获得的,那么使用易失性左操作数评估赋值表达式的副作用取决于是否使用表达式值完全任意,这将是一种奇怪的、意外的和记录不充分的行为。

4

2 回答 2

4

C11 澄清这是未指定的。

您可以在此处找到 C11 的最终草案。您现在引用的第二句话指的是脚注 111:

赋值运算符将值存储在左操作数指定的对象中。赋值表达式在赋值后具有左操作数的值,111)但不是左值。

脚注 111 说:

  1. 允许实现读取对象以确定值,但不是必需的,即使对象具有 volatile 限定类型。
于 2012-10-01T04:56:17.497 回答
-1

从常识来看,我会这样争论:

如果b = (whatever)并且whatever可以存储在寄存器中,则编译器没有理由重新评估表达式以进行赋值。也因为它不能比寄存器中的值更新。考虑f(x)vs. r = f(x):一旦f(x)知道结果,就可以分配它。因此,a = b = 1应该没有理由再次分配1b,只是为了能够分配给a。还假设你写a = ++b:显然b不能第二次增加;否则基本的 C 语义将被破坏。

于 2021-12-16T13:19:58.290 回答