首页 > 解决方案 > 编译器是否允许对本地 volatile 进行常量折叠?

问题描述

考虑这个简单的代码:

void g();

void foo()
{
    volatile bool x = false;
    if (x)
        g();
}

https://godbolt.org/z/I2kBY7

您可以看到既不优化gcc也不clang优化对g. 这在我的理解中是正确的:抽象机器假设volatile变量可能随时更改(例如由于硬件映射),因此将false初始化常量折叠到if检查中是错误的。

但是 MSVC 完全消除了对的调用(尽管g保持读取和写入!)。volatile这是符合标准的行为吗?


背景:我偶尔会使用这种结构来即时打开/关闭调试输出:编译器必须始终从内存中读取值,因此在调试期间更改变量/内存应该相应地修改控制流. MSVC 输出确实重新读取了该值,但忽略了它(可能是由于不断折叠和/或死代码消除),这当然违背了我的意图。


编辑:

标签: c++language-lawyervolatile

解决方案


TL;DR编译器可以对每个易失性访问执行任何操作。但是文档必须告诉您。-“通过 volatile glvalue 访问的语义是实现定义的。”


该标准为程序定义了允许的“易失性访问”序列和其他“可观察行为”(通过“副作用”实现),实现必须按照“'as-if'规则”遵守这些序列。

但是标准说(我的粗体强调):

工作草案,C++编程语言标准
文档编号:N4659
日期:2017-03-21

§ 10.1.7.1 cv 限定符

5 通过 volatile 泛左值访问的语义是实现定义的。[…]

同样对于交互式设备(我的粗体强调):

§ 4.6 程序执行

5 执行格式良好的程序的一致实现应产生与具有相同程序和相同输入的抽象机的相应实例的可能执行之一相同的可观察行为。[...]

7 对一致性实施的最低要求是:

(7.1) — 通过 volatile glvalues 的访问严格按照抽象机的规则进行评估。
(7.2) — 在程序终止时,写入文件的所有数据应与根据抽象语义执行程序可能产生的结果之一相同。
(7.3) — 交互设备的输入和输出动态应该以这样一种方式发生,即在程序等待输入之前实际交付提示输出。构成交互式设备的内容是实现定义的。

这些统称为程序的可观察行为。[...]

(无论如何,标准没有指定为程序生成的特定代码。)

因此,尽管标准规定不能从抽象机器副作用的抽象序列和某些代码(可能)定义的随之而来的可观察行为中省略易失性访问,但您不能期望任何东西会反映在目标代码或现实世界中行为,除非您的编译器文档告诉您什么是 volatile 访问。交互式设备同上。

如果您对 volatile 相对于抽象机器副作用的抽象序列和/或某些代码(可能)定义的随后的可观察行为感兴趣,那么就这么说吧。但是,如果您对生成的相应目标代码感兴趣,那么您必须在您的编译器和编译的上下文中解释它。

长期以来,人们错误地认为,对于易失性访问,抽象机器评估/读取会导致实现读取,而抽象机器分配/写入会导致实现写入。如果没有实施文档这样说,这种信念是没有根据的。当/iff 实现说它实际上在“易失性访问”上做了某些事情时,人们有理由期待某些事情——也许是某些目标代码的生成。


推荐阅读