之前在搜狗曾经看过一篇技术文章,详细讲述了gcc编译器的一个优化bug,也就是在开-O2的时候,汇编指令不对。但是没往心里去,逐渐淡忘。
前一段时间申请了虚机,就在上面yum install gcc,默认提供的还是4.1.2版本的。这也造成了我的一个悲剧(因为这个bug折腾了半天)。
一般没有人会去怀疑编译器的,是吧?
进入正题,最近在写一个小的网络程序,用C++写server,用java写client,因为网络传输涉及到byte order的问题,一般是统一转成big endian。
我发现有htonl,htons(当然他们的能力是32bit和16bit,我需要的是64bit,所以自己实现了一个htond),但是我要的数据是double,不好做位操作,怎么办呢?一个极其简单的方法就是将其转为long,64位机器long和double都是8字节。
double v;
uint64_t x = *(uint64_t*)&v;
x = htond(x);
v = *(double*)&x;我先用C++实现了一个客户端(本人对java不熟),但是接收到的数据和我传输的不一致。经过一步步打印,发现数据确实正确的传到了客户端,但是就是经过一个double-long-double的过程,数据打印出来就不对。我甚至怀疑,我的这个转换方法难道有错?
后来写了一个小实验程序,验证问题,发现开O2的时候,结果出错,如果是O0,结果正确。
int main() {
uint64_t z = 0x1234567890abcdef;
double x;
x = *(double*)&z;
uint64_t y = *(uint64_t*)&x;
printf( "%lx\n", y );
return 0;
}这段程序,在O2下输出0,在O0下输出1234567890abcdef。
对比了一下这段程序在两种情况下的汇编代码:
| O2 | O0 |
| |
可以看到,O2的情况下,也还是很傻的从%rax》16(%rsp)》%rax的挪动数据,并且将未被赋值的8(%rsp)的值先放在了%rsi,然后才更新了8(%rsp)的值,
导致%rsi里面是一个随机值。O0情况下,立即数是分次装入的,但是执行次序没有错,所以结果正确。
从这里我们可以猜测,调用printf时,将format字符串放在%edi,将需要打印的数据放在%rsi,将%eax清0,然后call printf就行了。
后来我下载了gcc 4.4.6,然后同样用O2编译,产生了正确的并且更简短的代码,如下:
main:
.LFB12:
.cfi_startproc
.cfi_personality 0x3,__gxx_personality_v0
subq $8, %rsp
.cfi_def_cfa_offset 16
movabsq $1311768467294899695, %rsi
movl $.LC0, %edi
xorl %eax, %eax
call printf
xorl %eax, %eax
addq $8, %rsp
.cfi_def_cfa_offset 8
ret
.cfi_endproc所以,如果有还在用老编译器的兄弟,赶紧更新吧。
GCC编译器Bug与数据类型转换
本文探讨了使用GCC 4.1.2版本编译器时遇到的一个问题,即在-O2优化级别下,double类型到uint64_t类型的转换出现错误的情况。通过对比不同优化级别下的汇编代码,揭示了问题原因,并提供了升级GCC版本后的解决方案。
8770

被折叠的 条评论
为什么被折叠?



