C ++ volatile关键字是否引入了内存栅栏? [英] Does the C++ volatile keyword introduce a memory fence?

查看:186
本文介绍了C ++ volatile关键字是否引入了内存栅栏?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我理解 volatile 通知编译器该值可能会更改,但为了完成此功能,编译器是否需要引入内存栅栏工作?



从我的理解,对volatile对象的操作序列不能重新排序,必须保留。这似乎暗示一些记忆栅栏是必要的,并没有真正的方法围绕这一点。






这个相关问题



乔纳森·威乐写的


...访问不同的volatile变量不能由
编译器重新排序,只要它们出现在单独的完整表达式中... right
volatile对于线程安全是无用的,但不是因为他
给出的原因。这不是因为编译器可能重新排序访问
volatile对象,而是因为CPU可能会重新排序它们。原子
操作和内存屏障阻止编译器和CPU从
重新排序


David Schwartz 回复


...没有区别, C ++标准的观点,
在编译器做某事之间,编译器发出
指令,使硬件做某事。如果CPU可以
重新排序访问挥发性物质,那么标准不需要保存它们的顺序
。 ...



... C ++标准没有区分
重排序。你不能认为CPU可以重新排序它们没有
可观察的效果,这是没关系的 - C ++标准定义他们的
顺序为可观察。编译器符合
平台上的C ++标准,如果它生成使平台执行
标准需要的代码。如果标准要求访问不是
的挥发物,则重新排序它们的平台不符合。 ...



我的观点是,如果C ++标准禁止编译器从
重新排序访问不同的挥发性,理论上
的这种访问是程序的可观察行为的一部分,
那么它还需要编译器发出禁止CPU
这样做的代码。该标准不区分
编译器做什么和编译器生成代码使CPU做什么。


产生两个问题:他们是正确吗? volatile

允许我解释什么时候应该使用 volatile





例如:

  volatile int * foo = some_memory_mapped_device; 
while(* foo)
; //等到* foo变为false

没有 volatile 说明符,编译器允许完全优化循环。 volatile 说明符告诉编译器它可能不会假设2个后续读取返回相同的值。



请注意, volatile 与线程无关。如果有一个不同的线程写入 * foo ,因为没有涉及获取操作,上面的例子不工作。



在所有其他情况下, volatile 的使用应被视为不可移植,不再通过代码审查,除非在处理预C ++ 11编译器和编译器扩展(如msvc的 / volatile:ms 开关,默认情况下在X86 / I64下启用)。


I understand that volatile informs the compiler that the value may be changed, but in order to accomplish this functionality, does the compiler need to introduce a memory fence to make it work?

From my understanding, the sequence of operations on volatile objects cannot be reordered and must be preserved. This seems to imply some memory fences are necessary and that there isn't really a way around this. Am I correct in saying this?


There is an interesting discussion at this related question

Jonathan Wakely writes:

... Accesses to distinct volatile variables cannot be reordered by the compiler as long as they occur in separate full expressions ... right that volatile is useless for thread-safety, but not for the reasons he gives. It's not because the compiler might reorder accesses to volatile objects, but because the CPU might reorder them. Atomic operations and memory barriers prevent the compiler and the CPU from reordering

To which David Schwartz replies in the comments:

... There's no difference, from the point of view of the C++ standard, between the compiler doing something and the compiler emitting instructions that cause the hardware to do something. If the CPU may reorder accesses to volatiles, then the standard doesn't require that their order be preserved. ...

... The C++ standard doesn't make any distinction about what does the reordering. And you can't argue that the CPU can reorder them with no observable effect so that's okay -- the C++ standard defines their order as observable. A compiler is compliant with the C++ standard on a platform if it generates code that makes the platform do what the standard requires. If the standard requires accesses to volatiles not be reordered, then a platform the reorders them isn't compliant. ...

My point is that if the C++ standard prohibits the compiler from reordering accesses to distinct volatiles, on the theory that the order of such accesses is part of the program's observable behavior, then it also requires the compiler to emit code that prohibits the CPU from doing so. The standard does not differentiate between what the compiler does and what the compiler's generate code makes the CPU do.

Which does yield two questions: Is either of them "right"? What do actual implementations really do?

解决方案

Rather than explaining what volatile does, allow me to explain when you should use volatile.

  • When inside an signal handler. Because writing to a volatile variable is pretty much the only thing the standard allows you to do from within a signal handler. Since C++11 you can use std::atomic for that purpose, but only if the atomic is lock-free.
  • When dealing with setjmp according to Intel.
  • When dealing directly with hardware and you want to ensure that the compiler does not optimize your reads or writes away.

For example:

volatile int *foo = some_memory_mapped_device;
while (*foo)
    ; // wait until *foo turns false

Without the volatile specifier, the compiler is allowed to completely optimize the loop away. The volatile specifier tells the compiler that it may not assume that 2 subsequent reads return the same value.

Note that volatile has nothing to do with threads. The above example is does not work if there was a different thread writing to *foo because there is no acquire operation involved.

In all other cases, usage of volatile should be considered non-portable and not pass code review anymore except when dealing with pre-C++11 compilers and compiler extensions (such as msvc's /volatile:ms switch, which is enabled by default under X86/I64).

这篇关于C ++ volatile关键字是否引入了内存栅栏?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆