将变量声明为“volatile”意味着直接从内存位置读取/写入,而不是从寄存器变量读取/写入。我对'序列点'有所了解。但我不明白标题中提到的陈述。
有人可以解释一下吗,并给出一些代码片段吗?
答案 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
。此外,由于v
是volatile
,编译器可能不会认为它是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的例子中,我们不希望稍后或者比编码它更早地读取它。