【问题标题】:volatile and double confusion易变和双重混乱
【发布时间】:2010-10-02 21:53:34
【问题描述】:
int x = 2;
volatile int y = 2;

const int z = x/y;

int main(){
    int x = 2 + 3;

    double d = 7 / 3;
}

我在这里有三个问题:

首先,在这种情况下,编译器可以计算出编译时'z'的值为1吗?

其次,我观察到编译器不会生成用于将 2 和 3 相加来初始化 x 的汇编指令。它直接用5初始化x。'd'也可以这样做吗?

第三,关于这两个概念有什么好书可以读吗?标准中的任何引用都会有所帮助(标准文档似乎是一个有趣的地方,虽然非常可怕)

【问题讨论】:

  • 既然你是为 x 做的,为什么不检查一下 d = 7 / 3 的组装说明是什么?
  • @Emile Cormier:涉及到 fld 和 fstp 指令。虽然我还没有放弃,但我不能理解太多
  • 顺便说一句,在这段代码中 d == 2,这是整数除法。
  • 等等,您检查了程序集是否有int = 2 + 3,但没有检查double d = 7 / 3

标签: c++ optimization floating-point volatile


【解决方案1】:

首先,在这种情况下,编译器可以计算出编译时'z'的值为1吗?

没有。读取或写入被认为具有副作用的 volatile 变量,因此不允许编译器这样做。

其次,我观察到编译器不会生成用于将 2 和 3 相加来初始化 x 的汇编指令。它直接用5初始化x。'd'也可以这样做吗?

是的。只要编译器能证明没有副作用。例如。如果在计算过程中发生溢出或被零除,它不能在编译时计算它,因为计算应该在运行时触发 CPU 异常。

第三,关于这两个概念有什么好书可以读吗?

是的。 C++ ISO 标准准确描述了您的要求。书籍是学习基础知识或理论的好书。编写重新表述标准中描述的所有技术细节的书籍是没有意义的。

【讨论】:

  • 无论如何,标准只描述了你将得到的结果,而不是编译器可以或应该做的优化。
  • 谢谢。我的疑问正是这个。如果编译器可以静态计算出 7/3 的值,那么它代表的是哪种格式的浮点结果? IEEE754是强制性的吗?
  • @Nivhus 我认为这取决于平台。仅保证在此平台上该值将被解释为 7/3。
  • +1,很好的答案,但最后一段听起来不准确。代码优化是一个实现细节。
  • @Johannes: "compiler could initialize z to 1 statically" 不。它不知道在运行时读取 y 会产生 2。所以它不能这样做。当谈到易失性内存时,标准谈到了副作用,引用:“访问由易失性glvalue(3.10)指定的对象,修改对象,调用库I / O函数或调用执行任何这些的函数操作都是副作用,是执行环境状态的变化。”以及“对易失性对象的访问严格按照抽象机的规则进行评估。”
【解决方案2】:

至于“第一”——y必须被访问,当z被初始化时,但我不认为访问的结果必须用于计算z如果 em> 实现以某种方式知道它必须是 2。对于这个程序,(我认为)只有 2 种方式可以具有任何其他值:

  1. 它被调试器修改或其他干扰程序。
  2. 加载程序将易失性全局变量放置在与硬件上的普通内存不同的内存区域中。 (在这种情况下,这确实是非常奇怪的实现定义的行为,以至于我认为编写的代码不合法​​,但是如果程序或程序外部的构建过程的某些部分可以以某种方式控制对象的最终位置)。

这些都是实现可以排除的事情 - 在第二种情况下,通过了解加载程序的行为,首先通过对您希望通过调试器实现的目标设置限制(“编写易失性变量会导致令人惊讶的行为”)。令调试器的用户失望,但该标准并没有限制调试器的工作方式,或者“实际”包含的内存,它只是限制了有效的 C++ 实现和程序做什么,以及 C++“看到”了什么。

实际上,您会认为编译器不会费心将 volatile 对象视为需要优化的对象。它是一个非常量对象,您必须怀疑定义非常量易失性对象的唯一原因是因为它会以编译器不期望的方式发生变化[*]。您可能希望它只是阅读 y 并进行除法,但我认为可以提出优化合法的案例。

至于“第二” - 对于您的程序,编译器可以在“as-if”规则下使用预先计算的值初始化d,前提是它知道值除法会产生什么。在你的程序中,它可以完全删除d

“如果它知道除法产生什么值”取决于实现 - 如果它支持对 IEEE 舍入模式或等效的更改,并且如果它不知道应该使用哪种模式,那么通常它不知道甚至是简单算术的结果。

“as-if”规则涵盖了例如 85% 的编译器优化。它包含在标准的第 1.9 节中,值得一看。我同意整个文档非常令人生畏,并且它使用的语言有时难以理解,但你必须从某个地方开始,所以从你当前感兴趣的任何内容开始;-)

[*] 具体来说,这不是 C++03 标准中以任何方式解决的问题,一些编译器 (Microsoft) 在其线程语义的定义中涉及 volatile

【讨论】:

  • 谢谢。所以我明白,除非编译器可以(以某种方式)证明 y 在库初始化时不是 2,否则编译器肯定会读取 y 来初始化 z。即使没有,它仍然是一种符合行为。对吗?
  • 在我的例子中,假设 'd' 没有被完全删除,我仍然怀疑 7/3 的值是否将在 IEEE754 标准中计算?如果目标架构不支持 IEEE754 怎么办?我认为这个问题不适用于简单的算术,例如 (2+3),它在所有平台上都是通用的。
  • 如果它不能证明它,它肯定会读为y 2.我不确定的是,它究竟在什么情况下能够证明它是2。如果它想在程序初始化的早期支持调试器正在更改的值,那么它当然无法证明这一点。如果它想支持y 位于读取某个输入端口的魔术地址,它无法证明这一点。但我认为你可以合法地编写一个 C++ 实现,其中 volatile 什么都不做,尽管 1.9/6,如果你知道实际上,读/写内存不是“可观察的”在这个硬件上 .
  • @Nivhus:编译器可能知道目标架构是否使用 IEEE 浮点数,也可能不知道。一些实现与特定硬件密切相关(或者可以与正确的命令行选项如此相关)。所以他们可能会假设 IEEE,或者编译器中可能内置了一个浮点仿真库,它可以假设结果将与库的结果匹配。其他实现支持许多不同类型的硬件,并且不想做任何假设。
  • 实际上,再想一想,我感觉允许实现预计算涉及浮点运算的常量表达式即使结果与运行时执行的相同计算不匹配.这不属于“as-if”规则,必须明确允许。如果我是对的,如果我能在标准中找到参考,我会更新。
猜你喜欢
  • 2020-08-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多