将变量声明为“易失性”意味着直接从内存位置读取/写入,而不是从寄存器变量。我对“序列点”有所了解。但我不明白标题中提到的陈述。
有人可以解释一下,并给出一些代码片段吗?
将变量声明为“易失性”意味着直接从内存位置读取/写入,而不是从寄存器变量。我对“序列点”有所了解。但我不明白标题中提到的陈述。
有人可以解释一下,并给出一些代码片段吗?
所有这些都在 C11 5.1.2.3 中描述
程序执行
/--/
访问 volatile 对象、修改对象、修改文件或调用执行这些操作的函数都是副作用,它们是执行环境状态的变化。表达式的评估通常包括值计算和副作用的启动。
之前排序是由单个线程执行的评估之间的不对称、传递、成对关系,这导致这些评估之间存在偏序。给定任意两个评估 A 和 B,如果 A 在 B 之前排序,则 A 的执行应先于 B 的执行。 ...
在表达式 A 和 B 的求值之间存在一个序列点意味着与 A 相关的每个值计算和副作用都在与 B 相关的每个值计算和副作用之前排序。
/--/
如果一个实际的实现可以推断出它的值没有被使用并且没有产生所需的副作用(包括调用函数或访问易失性对象引起的任何副作用),则它不需要评估表达式的一部分。
/--/
对一致性实现的最低要求是:
— 对 volatile 对象的访问严格按照抽象机的规则进行评估。
这不是很容易解释,但用简单的英语大致意思是:由于对 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
因为对 y 的访问必须在访问 z 之前进行排序。在分号处有一个序列点。但是如果变量不是易失的,编译器可以重新排序它们,如果它可以确定它不会影响程序的结果。
考虑这个故意设计的例子:
volatile int v = 5;
int x;
int y = (x=7), (x+v);
回想一下,逗号创建了一个序列点。因此,作业x=7
将在x+v
评估之前完成。此外,由于v
is volatile
,编译器可能不会认为这仅仅是因为在其声明和访问点之间5
没有代码修改:编译器必须生成一条指令来读取。v
v
然而,这给编译器留下了一个重要的决定:什么时候应该v
读取?更具体地说,在分配之前可以阅读v
x
吗?
这就是您的问题陈述的来源:
编译器可能不会跨序列点移动对 volatile 变量的访问
v
它明确禁止编译器在分配之前读取x
,因为这样做会将访问移动到由逗号运算符创建的序列点。v
如果没有这个限制,编译器将可以在分配之前或之后自由阅读x
。
在 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
不能评估 if 。volatile
*x
*y == 0
易失性意味着您的变量可能会在您的程序之外被修改,并且编译器无法通过在程序的表达式序列(指令)中移动它来优化它的访问。易失性使用的一个很好的例子是操作系统的滴答声。volatile 提示优化器说我们不希望他更改其访问权限:在 os ticks 的示例中,我们不想在编码位置之后或更快地读取它。