我了解到,
根据我的理解,对 volatile 对象的操作顺序不能被重新排序,必须保留。这似乎意味着一些 memory fences 是必要的,并且没有什么办法可以避免这种情况。我说得对吗?
这就产生了两个问题:它们中的任何一个都是“正确”的吗?实际的实现会做什么?
volatile
告诉编译器该值可能会被更改,但为了实现这个功能,编译器需要引入 memory fence 吗?根据我的理解,对 volatile 对象的操作顺序不能被重新排序,必须保留。这似乎意味着一些 memory fences 是必要的,并且没有什么办法可以避免这种情况。我说得对吗?
这个相关问题有一个有趣的讨论(链接)。
......只要它们出现在不同的完整表达式中,编译器就不能重新排序对不同易失变量的访问......他说易失变量对于线程安全是无用的这一点是正确的,但原因不是他所提供的。这不是因为编译器可能会重新排序对易失对象的访问,而是因为CPU可能会重新排序它们。原子操作和内存屏障可以防止编译器和CPU重新排序
David Schwartz在评论中回复如下:
从C++标准的角度来看,编译器执行某些操作与编译器发出导致硬件执行某些指令之间是没有区别的。如果CPU可以重新排序对易失变量的访问,则标准不要求保留它们的顺序。C++标准不对重新排序的具体实现作出任何区分。你不能认为CPU可以对它们进行重新排序而没有观察到影响,因此这是可以接受的——C++标准定义了它们的顺序是可观察的。如果标准要求对易失性不进行重排序,则在平台上生成使平台符合标准所需的代码的编译器符合C++标准。我想说的是,如果C++标准禁止编译器对不同易失性变量的访问进行重新排序,理由是这些访问的顺序是程序可观察行为的一部分,则它还要求编译器发出禁止CPU这样做的代码。标准不区分编译器的操作和编译器生成的代码使CPU执行的操作。这就产生了两个问题:它们中的任何一个都是“正确”的吗?实际的实现会做什么?
volatile
变量的读取。要么所有这些编译器都不符合标准,要么标准并不是你所认为的意思。(标准没有区分编译器做了什么和编译器让CPU做了什么。编译器的工作是生成代码,当运行时符合标准。) - David Schwartz