允许编译器对局部volatile进行常量折叠吗?

时间:2019-10-16 12:01:59

标签: c++ language-lawyer volatile

考虑以下简单代码:

void g();

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

https://godbolt.org/z/I2kBY7

您会看到gccclang都没有优化对g的潜在调用。在我的理解中,这是正确的:抽象机器假设volatile变量可能随时发生变化(由于例如被硬件映射),因此将false初始化不断地折叠到{{ 1}}检查将是错误的。

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


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


编辑:

  • 在此讨论对volatile的读取和写入的消除:Is it allowed for a compiler to optimize away a local volatile variable?(感谢Nathan!)。我认为该标准非常明确,即必须进行读写操作。但是,该讨论并未涵盖编译器将这些读取的<结果> 视为理所当然并基于此进行优化是否合法。我想这是标准中的under-/unspecified,但是如果有人证明我错了,我会很高兴。

  • 我当然可以将volatile设为非局部变量,以避开此问题。这个问题更多是出于好奇。

3 个答案:

答案 0 :(得分:2)

我认为[intro.execution](段号不同)可以用来解释MSVC行为:

  

具有自动存储持续时间的每个对象的实例与其块中的每个条目相关联。这样的对象存在,并且在块执行期间和块挂起时保留其最后存储的值

该标准不允许消除对易失性glvalue的读取,但以上段落可以解释为允许预测值false


BTW,C标准(N1570 6.2.4 / 2)表示

  

一个对象存在,具有一个恒定的地址,并在其生命周期内保留其最后存储的值。 34

     
     

34)对于易失性对象,最后一个存储不必在程序中是明确的。

目前尚不清楚在C内存/对象模型中是否可以将具有自动存储持续时间的非显式存储存储到对象中。

答案 1 :(得分:2)

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


该标准为程序定义了实现必须遵循“假设条件”的“易失性访问”和其他“可观察到的行为”(通过“副作用”实现)的允许序列。

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

  

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

  

§10.1.7.1简历限定符

     

5 通过易失性glvalue进行访问的语义是实现定义的。 […]

类似地,对于交互式设备(强调黑体字):

  

§4.6程序执行

     

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

     

7符合标准的实现的最低要求是:

     

(7.1)—严格根据抽象机的规则评估对易失性glvalue的访问。
  (7.2)—在程序终止时,写入文件的所有数据应与根据抽象语义执行程序可能产生的结果之一相同。
  (7.3)—交互设备的输入和输出动态必须以一种方式进行,即在程序等待输入之前,实际上会发出提示输出。 构成交互式设备的是实现定义的。

     

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

(标准未指定针对程序的任何生成什么代码。)

因此,尽管该标准指出,不能从某些机器代码(也许)定义的抽象机器副作用和随之而来的可观察行为的抽象序列中消除易失性访问,但是您不能指望任何东西会在目标代码或除非您的编译器文档告诉您什么是易失性访问,否则这些行为都是真实行为。交互式设备的同上。

如果您对抽象机副作用和/或某些代码(可能)定义的可观察行为的抽象序列感兴趣,则这样 。但是,如果您对生成的相应目标代码感兴趣,则必须在您的编译器&编译的上下文中对其进行解释。

长期人们错误地认为,对于易失性访问,抽象机评估/读取会导致实现读取,而抽象机分配/写入会导致实现写入。 没有实现文档的这种依据是没有根据的。当/仅当实现说它在“易失性访问”上确实做了某事时,人们就有理由期望那东西-也许是某些目标代码的生成。

答案 2 :(得分:-1)

我认为跳过支票是合法的。

每个人都喜欢引用的段落

  

34)对于易失性对象,不需要在程序中显式存储最后一个存储

并不意味着实现必须假定此类存储在任何时候或任何volatile变量中都是可能的。一个实现知道哪些存储是可能的。例如,完全合理的假设是,此类隐式写入仅发生在映射到设备寄存器的易失性变量上,并且此类映射仅适用于具有外部链接的变量。或者一种实现方式可以假定这种写操作只对字大小,字对齐的存储器位置进行了写操作。

话虽如此,我认为MSVC行为是一个错误。没有现实世界的理由可以优化通话。这样的优化可能是合规的,但这是不必要的。