C++ &引用;易挥发;限定符和编译器重新排序

C++ &引用;易挥发;限定符和编译器重新排序,c++,c,volatile,compiler-optimization,C++,C,Volatile,Compiler Optimization,编译器无法消除或重新排序对volatile限定变量的读/写操作 但是,如果存在其他变量,这些变量可能是或不可能是易失性的-限定的,那么情况又如何呢 情景1 编译器可以对第一个和第二个赋值,或者第三个和第四个赋值重新排序吗 情景2 同样的问题,编译器可以对第一个和第二个,或者第三个和第四个赋值进行重新排序吗?对于场景1,编译器不应该执行您提到的任何重新排序。对于场景2,答案可能取决于: 以及b和c变量是否在当前函数外可见(通过非本地或已传递其地址) 与您交谈的人(显然,对于C/C++中字符串vo

编译器无法消除或重新排序对
volatile
限定变量的读/写操作

但是,如果存在其他变量,这些变量可能是或不可能是
易失性的
-限定的,那么情况又如何呢

情景1 编译器可以对第一个和第二个赋值,或者第三个和第四个赋值重新排序吗

情景2
同样的问题,编译器可以对第一个和第二个,或者第三个和第四个赋值进行重新排序吗?

对于场景1,编译器不应该执行您提到的任何重新排序。对于场景2,答案可能取决于:

  • 以及
    b
    c
    变量是否在当前函数外可见(通过非本地或已传递其地址)
  • 与您交谈的人(显然,对于C/C++中字符串
    volatile
    的使用方式存在一些分歧)
  • 您的编译器实现
因此(软化我的第一个答案),我想说,如果您依赖于场景2中的某些行为,那么您必须将其视为不可移植代码,其在特定平台上的行为将由实现文档可能指示的任何内容决定(如果医生对此只字不提,那么你的行为肯定是不走运的

来自C99 5.1.2.3/2“程序执行”:

访问易失性对象、修改对象、修改文件或调用执行这些操作的函数都是副作用,都是执行环境状态的变化。表达式的求值可能会产生副作用。在执行序列中的某些指定点称为序列poin之前评估的所有副作用应完整,且后续评估的副作用均未发生

(第5段)一致性实施的最低要求是:

  • 在序列点,易失性对象是稳定的,因为之前的访问已经完成,后续的访问尚未发生
下面是Herb Sutter对C/C++中
volatile
访问所需行为的一点看法(来自“
volatile
vs.
volatile
”):

什么是附近普通的读写?那些仍然可以在不可优化的读写中重新排序吗?今天,没有实用的可移植答案,因为C/C++编译器的实现方式变化很大,不可能在任何时候收敛。例如,C++标准的一种解释认为普通的读可以自由移动。在C/C++易失性读写过程中,在任一方向上都是不稳定的,但普通的写操作根本不能在C/C++易失性读写过程中移动——这将使C/C++易失性的限制性和限制性分别低于有序原子。一些编译器供应商支持这种解释;另一些编译器供应商则不在整个volati过程中进行优化le可以读或写;还有一些人有自己喜欢的语义

值得一提的是,Microsoft为C/C++
volatile
关键字(如Microsoft sepcific)编写了以下文档:

  • 对易失性对象的写入(volatile write)具有释放语义;在指令序列中对易失性对象的写入之前发生的对全局或静态对象的引用将在编译二进制文件中的易失性写入之前发生

  • 易失性对象的读取(volatile read)具有Acquire语义;在指令序列中读取易失性内存之后发生的对全局或静态对象的引用将在编译二进制文件中的易失性读取之后发生

这允许易失性对象用于多线程应用程序中的内存锁定和释放


易失性不是内存围栏。在代码段2中对B和C的赋值可以被消除或执行。为什么你要在2中声明导致1的行为?

< P> C++标准说:(1.9/6):< /P> 可观察到的行为 抽象机器是它的序列 读取和写入易失性数据和 对库I/O函数的调用

在场景1中,您提出的任何更改都会更改对易失性数据的写入顺序

在场景2中,您提出的更改都不会更改序列。因此,根据“仿佛”规则(1.9/1),它们是允许的:

…一致性实现是 需要(仅)模拟 抽象的可观察行为 机器

为了判断是否发生了这种情况,您需要检查机器代码,使用调试器,或者引发未定义或未指定的行为,这些行为的结果恰好是您在实现中知道的。例如,实现可能会保证并发执行的线程具有相同的内存,但这是错误的IDE的C++标准的范围。因此,虽然标准可能允许特定的代码转换,但特定的实现可以排除它,理由是它不知道代码是否将在多线程程序中运行。
如果要使用可观察行为来测试是否发生了重新排序(例如,打印上述代码中的变量值)标准当然不允许这样做。

一些编译器将对易失性限定对象的访问视为内存隔离。其他编译器则不允许。有些程序编写时要求易失性作为隔离。其他编译器则不允许

在提供围栏的平台上运行的需要围栏的代码可能比在不提供围栏的平台上运行的不需要围栏的代码运行得更好,但如果不提供围栏,需要围栏的代码将出现故障。不需要围栏的代码通常运行缓慢
volatile int a;
volatile int b;

a = 1;
b = 2;
a = 3;
b = 4;
volatile int a;
int b, c;

b = 1;
a = 1;
c = b;
a = 3;