C++ 是否保证保留对volatile结构的各个成员的写入顺序?

C++ 是否保证保留对volatile结构的各个成员的写入顺序?,c++,c,concurrency,language-lawyer,volatile,C++,C,Concurrency,Language Lawyer,Volatile,假设我有这样一个结构: volatile struct { int foo; int bar; } data; data.foo = 1; data.bar = 2; data.foo = 3; data.bar = 4; data.bar = 4; data.foo = 3; 作业是否保证不会重新排序 例如,如果没有volatile,编译器显然可以将其优化为两条不同顺序的指令,如下所示: volatile struct { int foo; int bar; } data; data.fo

假设我有这样一个结构:

volatile struct { int foo; int bar; } data;
data.foo = 1;
data.bar = 2;
data.foo = 3;
data.bar = 4;
data.bar = 4;
data.foo = 3;
作业是否保证不会重新排序

例如,如果没有volatile,编译器显然可以将其优化为两条不同顺序的指令,如下所示:

volatile struct { int foo; int bar; } data;
data.foo = 1;
data.bar = 2;
data.foo = 3;
data.bar = 4;
data.bar = 4;
data.foo = 3;
但是对于volatile,编译器是否不需要执行类似的操作

data.foo = 1;
data.foo = 3;
data.bar = 2;
data.bar = 4;
(例如,将成员视为独立的、不相关的易失性实体,并进行重新排序,我可以想象,如果foo和bar位于页面边界,它可能会尝试改进引用的局部性。)

也,对于C和C++标准的当前版本,答案是否一致?

它们将不会重新排序

C17 6.5.2.3(3)规定:

后缀表达式,后跟。运算符和标识符指定结构的成员 或联合对象。该值是命名成员的值(97),如果第一个表达式为 左撇子。如果第一个表达式具有限定类型,则结果具有该类型的限定版本 指定成员的姓名

由于
data
具有
volatile
限定类型,因此
data.bar
data.foo
也具有限定类型。因此,您要对
volatile int
对象执行两个赋值。以及6.7.3脚注136

不应通过以下方式“优化”如此声明的对象上的操作[作为
volatile
] 一种实现或重新排序,除非计算表达式的规则允许


一个更微妙的问题是,编译器是否可以用一条指令同时分配它们,例如,如果它们是连续的32位值,它是否可以使用64位存储来设置这两个值?我认为不会,至少GCC和Clang不会尝试这样做。

如果您想在多个线程中使用此功能,有一个重要的问题

虽然编译器不会对
volatile
变量的写入进行重新排序(如中所述),但还有一点可以进行写入重新排序,那就是CPU本身。这取决于CPU体系结构,下面是几个示例:

英特尔64 看

虽然存储指令本身没有重新排序(2.2):

  • 门店不会与其他门店一起重新排序
  • 它们可能以不同的顺序对不同的CPU可见(2.4):

    “英特尔64内存排序”允许用户以不同的顺序查看两个处理器的存储 那两个处理器

    AMD64 AMD 64(普通x64)在以下方面具有类似的行为:

    通常,不允许无序写入。在所有以前的指令都按程序顺序完成之前,无序执行的写指令无法将其结果提交(写入)内存。然而,处理器可以将无序写入指令的结果保存在专用缓冲区(软件不可见)中,直到该结果可以提交到内存

    PowerPC 我记得在以下情况下必须小心:

    虽然Xbox 360 CPU不会对指令进行重新排序,但会重新安排写入操作,写入操作会在指令本身完成后完成。PowerPC内存模型特别允许这种写操作的重新排列

    为了避免CPU以可移植的方式重新排序,您需要像C++11或C11一样使用。如果没有它们,从另一个线程看到的写入顺序可能会不同

    另见

    维基百科的文章中也提到了这一点:

    此外,由于缓存、缓存一致性协议和宽松的内存顺序,无法保证其他处理器或内核以相同的顺序查看易失性读写,这意味着单凭易失性变量甚至可能无法作为线程间标志或互斥量


    我不知道,但我确实希望如此,否则我使用的中断结构的队列结构可能有问题:)这里没有为C++重新排序完整的引用(C可能是不同的)-“一个类型是易失性的对象,或者是一个易失性对象的子对象”…“为了优化,通过volatile限定类型的glvalue表达式进行的每次访问(读或写操作、成员函数调用等)都被视为一种可见的副作用”@NateEldredge我从未想过将
    std::atomic
    volatile
    结合起来。如果op为IO交互公开该结构,那么利用
    volatile
    是毫无疑问的。然而op的标签表明它是关于并发(多线程程序)的,在这种情况下,
    std::atomic
    是正确的工具,而不是
    volatile
    。也许这只是一个松散的标签命名样式。@血腥主要是我在看C,但是由于语言之间经常有细微的差别(C++似乎已经很久没有成为超集的目标)了,我特别想知道它是否会挥发,因为它将应用到C代码到C++的移植性。是的,C++确实有更好的库来处理这种事情。编译器不必做任何事情,什么是易失性访问是定义的实现,标准只是定义了在可观察行为和抽象机器方面的访问上的某种排序关系。有关实施文档,请参阅。标准中没有涉及代码生成。感谢您引用标准(因为我碰巧没有副本),这似乎回答了问题,但您的文本“您正在分配两个volatile int对象”具有误导性,因为如果它们不被视为同一对象,则答案将不同,或者需要对编译器进行额外的限制,以保留易失性访问的顺序,即使它们位于不相关的对象中。也许最好保留引号并细化答案文本……我认为将操作更改为同时进行(使用一条指令完成两项作业)应该算作重新排序。如果不是通过严格的标准解释,那么肯定是通过标准的精神,即