为什么优化器会删除我的代码?
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
这是为什么?
其他观察:
如果我展开循环,用 -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;
}
如果我遇到非常相似的问题,但对于 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
及其 unsigned
和 signed
变体)
这是不可移植的代码,但是,如果你仍然想合法地做它,你能做些什么呢?
- 从
uint64_t
复制到uint16_t
数组(通过memcpy
或std::copy
),修改值,复制回来。
- 或使用直接转换为矢量化指令的编译器内部结构
- 或禁用严格别名。
今天我偶然发现了一个奇怪的问题。考虑这个我尝试模拟 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
这是为什么?
其他观察:
如果我展开循环,用
-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; }
如果我遇到非常相似的问题,但对于 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
及其 unsigned
和 signed
变体)
这是不可移植的代码,但是,如果你仍然想合法地做它,你能做些什么呢?
- 从
uint64_t
复制到uint16_t
数组(通过memcpy
或std::copy
),修改值,复制回来。 - 或使用直接转换为矢量化指令的编译器内部结构
- 或禁用严格别名。