c++ - 编译器是否允许对本地 volatile 进行常量折叠?
问题描述
考虑这个简单的代码:
void g();
void foo()
{
volatile bool x = false;
if (x)
g();
}
您可以看到既不优化gcc
也不clang
优化对g
. 这在我的理解中是正确的:抽象机器假设volatile
变量可能随时更改(例如由于硬件映射),因此将false
初始化常量折叠到if
检查中是错误的。
但是 MSVC 完全消除了对的调用(尽管g
保持读取和写入!)。volatile
这是符合标准的行为吗?
背景:我偶尔会使用这种结构来即时打开/关闭调试输出:编译器必须始终从内存中读取值,因此在调试期间更改变量/内存应该相应地修改控制流. MSVC 输出确实重新读取了该值,但忽略了它(可能是由于不断折叠和/或死代码消除),这当然违背了我的意图。
编辑:
这里讨论了读取和写入的消除
volatile
:是否允许编译器优化掉局部 volatile 变量?(感谢内森!)。我认为标准非常明确,即必须进行这些读取和写入。但是该讨论并未涵盖编译器是否可以将这些读取的结果视为理所当然并在此基础上进行优化。我想这是标准中未指定/未指定的,但如果有人证明我错了,我会很高兴。我当然可以制作
x
一个非局部变量来回避这个问题。这个问题更多是出于好奇。
解决方案
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 实现说它实际上在“易失性访问”上做了某些事情时,人们有理由期待某些事情——也许是某些目标代码的生成。
推荐阅读
- javascript - React Native“样式化组件”不起作用
- asp.net - ArgumentNullException:值不能为空。(参数“项目”)使用具有角色授权的身份的注册表单
- java - 获取 IllegalStateException : Session/EntityManager 在运行 IntegrationTests 时关闭异常,同时运行正常的应用程序运行正常
- java - 即使已设置映射,Spring Boot 仍返回错误 404
- sql - PL/SQL:表达式类型错误,将游标数据分配给 VARRAY
- c# - 反序列化以 @ 开头的 JSON 属性
- email - 更改发件人和“转发”邮件
- java - 剪贴板中的 Java 粘贴不适用于 Linux 上的每个应用程序
- package - Genera: 解锁包裹
- python - Docker 映像构建:如何为 armv7 架构安装 python 包 google-cloud-bigquery 和 numpy、scipy 和 pandas (Miniconda3)?