考虑以下简单代码:
void g();
void foo()
{
volatile bool x = false;
if (x)
g();
}
Run Code Online (Sandbox Code Playgroud)
您可以看到,也gcc没有clang优化对的潜在调用g。在我的理解中,这是正确的:抽象机器假定volatile变量随时可能更改(由于例如被硬件映射),因此将false初始化不断折叠到if检查中将是错误的。
但是MSVC g完全消除了对的调用(保留对文件的读写volatile!)。这是符合标准的行为吗?
背景:我有时会使用这种结构来即时打开/关闭调试输出:编译器必须始终从内存中读取值,因此在调试过程中更改变量/内存应相应地修改控制流。 。MSVC输出确实重新读取了该值,但忽略了该值(可能是由于不断折叠和/或消除了死代码),这当然违背了我的意图。
编辑:
volatile这里讨论了对读写的消除:是否允许编译器优化局部volatile变量?(感谢内森!)。我认为该标准非常明确,即必须进行那些读写操作。但是,该讨论并未涵盖编译器将这些读取结果视为理所当然并基于此进行优化是否合法。我想这在标准中未指定/未指定,但是如果有人证明我做错了,我会很高兴。
我当然可以制作x一个非局部变量来避免该问题。这个问题更多是出于好奇。
据我所知,编译器从不优化声明为的变量volatile.但是,我有一个像这样声明的数组.
volatile long array[8];
Run Code Online (Sandbox Code Playgroud)
不同的线程读写它.数组的元素仅由其中一个线程修改,并由任何其他线程读取.但是,在某些情况下,我注意到即使我从一个线程修改一个元素,读取它的线程也不会注意到这个变化.它继续读取相同的旧值,就好像编译器已将其缓存在某处.但是编译器本身不应该缓存volatile变量,对吧?那怎么会发生这种情况.
注意:我不是volatile用于线程同步,所以请停止给我答案,如使用锁或原子变量.我知道volatile,atomic变量和互斥量之间的区别.另请注意,该体系结构是x86,具有主动缓存一致性.在我认为变量被其他线程修改后,我也读了很长时间.即使经过很长一段时间,阅读线程也看不到修改后的值.
标准定义了哪个volatile变量可以不被检测到?
我发现了两个关于volatile的规范性文本:
读取由volatile glvalue([basic.lval])指定的对象,修改对象,调用库I/O函数或调用执行任何这些操作的函数都是副作用,这些都是状态的变化.执行环境.表达式(或子表达式)的评估通常包括值计算(包括确定用于glvalue评估的对象的身份以及获取先前分配给用于prvalue评估的对象的值)和启动副作用.当对库I/O函数的调用返回或通过volatile glvalue进行访问时,即使调用所隐含的某些外部操作(例如I/O本身)或易失性访问,也会认为副作用已完成可能尚未完成.
这段是关于未被发现的变化吗?请问副作用这个意思?
或者有dcl.type.cv/5:
通过volatile glvalue进行访问的语义是实现定义的.如果尝试通过使用非易失性glvalue访问使用volatile限定类型定义的对象,则行为未定义.
这个段落是关于我的问题吗?什么"通过volatile glvalue进行访问的语义是实现定义的"究竟是什么意思?你能举一个不同的"访问语义"的例子吗?
还有dcl.type.cv/6,这是关于我的问题,但它只是一个注释:
[注意:volatile是对实现的暗示,以避免涉及对象的激进优化,因为对象的值可能会被实现无法检测到的方式更改.此外,对于某些实现,volatile可能指示访问对象需要特殊的硬件指令.有关详细语义,请参阅[intro.execution].一般来说,volatile的语义在C++中与在C中的相同. - 最后的注释]
看看这个小片段:
struct A {
virtual ~A() { }
};
struct B { };
bool fn() {
A *volatile a = new A;
return dynamic_cast<B *>(a);
}
Run Code Online (Sandbox Code Playgroud)
编译器是否允许dynamic_cast完全删除,并转换dynamic_cast为简单nullptr;?
这个问题的原因是这个答案.
笔记:
假设volatile意味着编译器不能假设任何东西a,因为它是volatile.这是一个问题的原因.
dynamic_cast可能不被允许删除的事实是程序中某处可能存在某种类型,它来源于A和B.