不稳定和双重混乱

发布于 2024-09-25 19:28:32 字数 332 浏览 1 评论 0原文

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' 也可以做同样的事情吗?

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

int x = 2;
volatile int y = 2;

const int z = x/y;

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

    double d = 7 / 3;
}

I have three questions here:

Firstly, can the compiler calculate the value of the 'z' at compile time to be 1 in this case?

Secondly, I observed that the compiler does not generate assembly instructions for adding 2 and 3 to initialize x. It directly initializes x with 5. Can the same be done with 'd'?

Thirdly, Is there any good book to read on these two concepts? Any quotes from the Standard would be helpful (The standard document seems to be an interesting place though very scary)

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

乙白 2024-10-02 19:28:33

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

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

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

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

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

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

“as-if”规则涵盖了 85% 的编译器优化。标准第 1.9 节对此进行了介绍,值得一看。我同意整个文档相当令人生畏,而且它使用的语言有时令人费解,但你必须从某个地方开始,所以从你当前感兴趣的任何内容开始;-)

[*] 具体来说,这不是无论以何种方式在 C++03 标准中解决了这一问题,某些编译器 (Microsoft) 在其线程语义定义中都涉及 易失性

As for "first" - y must be accessed, when z is initialized, but I don't think the result of that access has to be used to calculate z, if the implementation somehow knows that it must be 2. For this program there are (I think) only 2 ways it can have any other value:

  1. it's modified by a debugger or other interference with the program.
  2. the loader places volatile globals in some region of memory that doesn't behave like normal memory on your hardware. (in this case that would be very odd implementation-defined behavior indeed, to the point I don't think it's legal for the code as written, but it's relevant if the program, or some part of the build process outside the program, can somehow control where the object ends up).

Both of those are things that the implementation can rule out - in the second case by knowing how the loader behaves, in the first place by placing limitations on what you can hope to achieve with a debugger ("writing volatile variables results in surprising behavior"). Disappointing for the user of the debugger, but the standard doesn't constrain how debuggers work, or what memory "actually" contains, it just constrains what valid C++ implementations and programs do, and what C++ "sees".

In practice you'd think the compiler wouldn't bother treating a volatile object as subject to optimisations. It's a non-const object, and you've got to suspect that the only reason for defining a non-const volatile object is because it's going to change in ways the compiler doesn't expect[*]. You'd hope it will just read y and do the division, but I reckon a case could be made for optimisation being legal.

As for "second" - in the case of your program, the compiler can initialize d with a pre-computed value under the "as-if" rule, provided it knows what value division produces. For that matter in your program it can remove d entirely.

"Provided it knows what value division produces" depends on the implementation - if it supports changes to IEEE rounding modes or equivalent, and if it doesn't know what mode should be in force, then in general it doesn't know in advance the result of even simple arithmetic.

The "as-if" rule covers, say, 85% of compiler optimisations. It's covered in section 1.9 of the standard, which is worth a look. I agree that the document as a whole is pretty intimidating, and the language it uses is sometimes impenetrable, but you have to start somewhere, so start with whatever you're currently interested in ;-)

[*] Specifically, and this is not something that's addressed in the C++03 standard in any way, some compilers (Microsoft) involve volatile in their definition of threading semantics.

像极了他 2024-10-02 19:28:32

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

不可以。读取或写入易失性变量被认为有副作用,因此编译器不允许这样做。

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

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

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

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

Firstly, can the compiler calculate the value of the 'z' at compile time to be 1 in this case?

No. reading or writing to volatile variables considered having side effects, so the compiler is not allowed to do this.

Secondly, I observed that the compiler does not generate assembly instructions for adding 2 and 3 to initialize x. It directly initializes x with 5. Can the same be done with 'd'?

Yes. As long as the compiler can prove that there are no side effect. E.g. if overflow or a divide by zero happens during the computation, it can't compute it at compile time since the computation should trigger a CPU exception at runtime.

Thirdly, Is there any good book to read on these two concepts?

Yes. C++ ISO standard describes exactly what you're asking. Books are good to learn the basics or the theory. It makes no sense writing books that rephrase all the technical details described in the standard.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文