为什么优化器会删除我的代码?

Why does the optimizer remove my code?

今天我偶然发现了一个奇怪的问题。考虑这个我尝试模拟 MMX's PADDW instruction:

的简单程序
#include <cstdint>
#include <cstdio>

int main()
{
    uint64_t a = 0;
    uint64_t b = 0x1234123412341234;

    uint64_t c = 0;
    uint16_t *a_words = reinterpret_cast<uint16_t*>(&a);
    uint16_t *b_words = reinterpret_cast<uint16_t*>(&b);
    uint16_t *c_words = reinterpret_cast<uint16_t*>(&c);

    for (size_t i = 0; i < 4; i ++)
        c_words[i] = a_words[i] + b_words[i];

    printf("%d %d %d %d\n", a_words[0], a_words[1], a_words[2], a_words[3]);
    printf("%d %d %d %d\n", b_words[0], b_words[1], b_words[2], b_words[3]);
    printf("%d %d %d %d\n", c_words[0], c_words[1], c_words[2], c_words[3]);
    printf("%016llx\n", c);
    return 0;
}

编译它和 运行 g++ -std=c++11 test.cpp -o test && ./test 结果如下:

0 0 0 0
4660 4660 4660 4660
4660 4660 4660 4660
1234123412341234

但是,如果我启用 -O2,它会显示错误的值(在 -O1 上它仍然有效):

0 0 0 0
4660 4660 4660 4660
4660 4660 4660 4660
0000000000000000

这是为什么?


其他观察:

  1. 如果我展开循环,用 -O2 编译就可以了 (!!):

    #include <cstdint>
    #include <cstdio>
    
    int main()
    {
        uint64_t a = 0;
        uint64_t b = 0x1234123412341234;
    
        uint64_t c = 0;
        uint16_t *a_words = reinterpret_cast<uint16_t*>(&a);
        uint16_t *b_words = reinterpret_cast<uint16_t*>(&b);
        uint16_t *c_words = reinterpret_cast<uint16_t*>(&c);
    
        c_words[0] = a_words[0] + b_words[0];
        c_words[1] = a_words[1] + b_words[1];
        c_words[2] = a_words[2] + b_words[2];
        c_words[3] = a_words[3] + b_words[3];
    
        printf("%d %d %d %d\n", a_words[0], a_words[1], a_words[2], a_words[3]);
        printf("%d %d %d %d\n", b_words[0], b_words[1], b_words[2], b_words[3]);
        printf("%d %d %d %d\n", c_words[0], c_words[1], c_words[2], c_words[3]);
        printf("%016llx\n", c);
        return 0;
    }
    
  2. 如果我遇到非常相似的问题,但对于 32 位整数而不是 64 位整数,它也能正常工作:

    #include <cstdint>
    #include <cstdio>
    
    int main()
    {
        uint32_t a = 0;
        uint32_t b = 0x12121212;
    
        uint32_t c = 0;
        uint8_t *a_words = reinterpret_cast<uint8_t*>(&a);
        uint8_t *b_words = reinterpret_cast<uint8_t*>(&b);
        uint8_t *c_words = reinterpret_cast<uint8_t*>(&c);
    
        for (size_t i = 0; i < 4; i ++)
            c_words[i] = a_words[i] + b_words[i];
    
        printf("%d %d %d %d\n", a_words[0], a_words[1], a_words[2], a_words[3]);
        printf("%d %d %d %d\n", b_words[0], b_words[1], b_words[2], b_words[3]);
        printf("%d %d %d %d\n", c_words[0], c_words[1], c_words[2], c_words[3]);
        printf("%08x\n", c);
        return 0;
    }
    

该问题在 32 位和 64 位计算机上均重复出现。在 Cygwin 上尝试了 g++ (GCC) 4.9.2,在 GNU/Linux 上尝试了 g++ (Debian 4.9.1-19) 4.9.1

这是严格的别名违规。您将 A 类型的值写入存储 B 类型对象的内存。C++ 标准规定您不能这样做(此规则的例外是 char 及其 unsignedsigned 变体)

这是不可移植的代码,但是,如果你仍然想合法地做它,你能做些什么呢?

  • uint64_t复制到uint16_t数组(通过memcpystd::copy),修改值,复制回来。
  • 或使用直接转换为矢量化指令的编译器内部结构
  • 或禁用严格别名。