编译器可能不会跨序列点移动对volatile变量的访问;这是什么意思?

时间:2012-11-02 14:28:04

标签: c compiler-construction volatile sequence-points

将变量声明为“volatile”意味着直接从内存位置读取/写入,而不是从寄存器变量读取/写入。我对'序列点'有所了解。但我不明白标题中提到的陈述。

有人可以解释一下吗,并给出一些代码片段吗?

4 个答案:

答案 0 :(得分:3)

所有这些都在C11 5.1.2.3

中描述
  

程序执行

     

/ - /

     

访问易失性对象,修改对象,修改文件或调用函数   任何这些操作都是副作用,这是状态的变化   执行环境。表达式的评估通常包括两个值   计算和引发副作用。

     

之前排序是评估之间的不对称,传递,成对关系   由单个线程执行,这导致这些评估中的部分顺序。   给定任何两个评估A和B,如果A在B之前被排序,则执行A   应在执行B之前。   ...

     

在表达式A和B的评估之间存在序列点意味着在与B相关联的每个值计算和副作用之前,对与A相关联的每个值计算和副作用进行排序。

     

/ - /

     

实际实现不需要评估表达式的一部分,如果它可以推断出它的值没有被使用并且没有产生所需的副作用(包括由调用函数或访问volatile对象引起的任何副作用)。

     

/ - /

     

符合实施的最低要求是:

     

- 严格按照摘要规则评估对易失性对象的访问   机。

这不是很容易理解,但它在简单的英语中大致意味着:因为易失性对象的访问是副作用,所以不允许编译器优化这种访问,也不允许编程它们可能以不同的顺序运行,为了在具有指令缓存/分支预测的CPU上获得更好的性能,或者只是更好的性能,因为它具有方便存储在某些CPU寄存器中的某些值。

(C11标准还明确声明不保证volatile对象是线程安全的,上下文切换后volatile对象的值是未指定的行为。)

编辑示例

给出代码

volatile int x;
volatile int y;
volatile int z;

x=i;
y=something;
z=i;

然后编译器不允许将可执行文件中的指令重新排序为

x=i;
z=i;
y=something

因为必须在访问z之前对y的访问进行排序。半结肠有一个序列点。但是如果变量不是易变的,那么编译器可以重新排序它们,如果它可以确定它不会影响程序的结果。

答案 1 :(得分:2)

考虑这个刻意设想的例子:

volatile int v = 5;
int x;
int y = (x=7), (x+v);

回想一下,逗号会创建一个序列点。因此,在评估x=7之前,将完成作业x+v。此外,由于vvolatile,编译器可能不会认为它是5,因为在它的声明和访问点之间没有修改v的代码:编译器必须生成读取 v的指令。

然而,这给编译器留下了一个重要的决定:何时应该v读取?更具体地说,在分配v 之前阅读x 是否可以?

这是你问题陈述的来源:

  

编译器可能无法跨序列点移动对volatile变量的访问

它明确禁止编译器在分配v之前读取x,因为这样做会将访问移动到逗号运算符创建的序列点。如果没有此限制,编译器可以在分配v之前或之后自由阅读x

答案 2 :(得分:1)

在C ++“抽象机器”的上下文中,程序由执行的volatile访问序列(“可观察行为”)定义,优化器可以更改任何其他内容。

例如,如果您的CPU具有许多寄存器,则编译器将对象存储在其中是完全可以接受的,除非它们被声明为volatile。查看CPU执行的内存访问的人将不再看到程序执行的所有操作,但保证volatile访问将在那里。

如果程序使用与未优化程序相同的数据生成相同的volatile访问序列,则程序会正确优化。

序列点的概念存在于另一层 - 这涉及抽象机器内操作的顺序。如果您有两个没有序列点的操作,则没有排序保证,这就是x = 0; return x++ + x++;没有定义结果的原因。

将这两个概念放在一起很困难,因为非volatile次访问的保证很少。我能想到的唯一例子是

int *y = ...;
volatile int *x = ...;
std::exception up;

if (*y == 0)
    throw up;

return *x;

由于*y不是volatile,所以在任何地方移动对其的任何访问都是完全可以接受的,如果可能的话甚至可以优化它们,但在任何情况下都不能评估*x *y == 0 {1}}。

答案 3 :(得分:0)

易失性意味着您的变量可能会在程序之外被修改,并且编译器无法通过在程序的表达式序列(指令)中移动它来优化其访问。 易失性使用的一个很好的例子是操作系统滴答声。 volatile是优化器提示我们不希望他改变其访问权限的提示:在os ticks的例子中,我们不希望稍后或者比编码它更早地读取它。