优化和严格别名

Optimisation and strict aliasing

我的问题是关于代码片段,如下所示:

#include <iostream>

int main() {
    double a = -50;
    std::cout << a << "\n";
    uint8_t* b = reinterpret_cast<uint8_t*>(&a);
    b[7] &= 0x7F;
    std::cout << a << "\n";
    return 0;
}

据我所知,我没有违反任何规则,所有内容都定义明确(如下所述,我忘记了 uint8_t 不允许为其他类型起别名)。有一些实现定义的行为正在进行,但就这个问题而言,我认为这无关紧要。

我希望此代码打印 -50,然后在 double 遵循 IEEE 标准的系统上打印 50,长度为 8 字节并存储在小端格式。现在的问题是。编译器是否保证会发生这种情况。更具体地说,启用优化后,编译器可以显式或隐式地优化掉中间的 b[7],只需将 a 保留在整个函数的寄存器中。第二个显然可以通过指定 volatile double a 来解决,但是需要吗?

编辑:作为一个注释,我(错误地)记得 uint8_t 必须是 unsigned char 的别名,但实际上标准没有指定这样的。我还以某种方式编写了问题,是的,编译器可以提前知道这里的所有内容,但修改为

#include <iostream>

int main() {
    double a;
    std::cin >> a;
    std::cout << a << "\n";
    unsigned char* b = reinterpret_cast<unsigned char*>(&a);
    b[7] &= 0x7F;
    std::cout << a << "\n";
    return 0;
}

大家可以看出问题出在哪里。这里不再违反严格的别名规则,并且 a 不是编译时常量。然而,Richard Critten 的评论很好奇是否可以检查别名数据,但不能写入,有没有一种方法可以设置单个字节,同时仍然遵循标准?

More specifically, turning on optimisations can the compiler optimise away the middle b[7], either explicitly or implicitly, by simply keeping a in a register through the whole function.

编译器可以生成双精度值 50 作为常量,并将其直接传递给输出函数。 b 可以完全优化掉。像大多数优化一样,这是由于 as-if 规则:

[intro.abstract]

The semantic descriptions in this document define a parameterized nondeterministic abstract machine. This document places no requirement on the structure of conforming implementations. In particular, they need not copy or emulate the structure of the abstract machine. Rather, conforming implementations are required to emulate (only) the observable behavior of the abstract machine as explained below.


The second one obviously could be solved by specifying volatile double a

这会阻止优化,优化通常被认为是解决方案的对立面。


Does the compiler guarantee that [50 is printed].

你没有提到你问的是什么编译器。我假设你的意思是标准是否保证了这一点。它不能保证普遍适用。您依赖于关于实施的几个假设:

  • 如果sizeof(double) < 8,则您访问对象超出其范围,程序的行为未定义。
  • 如果 std::uint8_t 不是 unsigned char 的类型别名,则不允许使用别名 double,程序的行为未定义。

鉴于假设成立,因此行为是 well-defined,那么第二个输出将是一个 double 值,类似于 -50,但其最高有效位(从第 8 位开始)位置 7 的字节将被设置为 0。在 little endian IEEE-754 表示的情况下,该值将为 50。不需要 volatile 来保证这一点,它不会在中添加保证如果程序的行为未定义。