我在看似无害的函数中有一些未定义的行为,它从缓冲区解析double
值。我分两部分阅读double
,因为我有理由确定语言标准表示移位char
值仅在32位上下文中有效。
inline double ReadLittleEndianDouble( const unsigned char *buf )
{
uint64_t lo = (buf[3] << 24) | (buf[2] << 16) | (buf[1] << 8) | buf[0];
uint64_t hi = (buf[7] << 24) | (buf[6] << 16) | (buf[5] << 8) | buf[4];
uint64_t val = (hi << 32) | lo;
return *(double*)&val;
}
由于我将32位值存储到64位变量lo
和hi
中,我合理地期望这些变量的高位32位始终为0x00000000
。但有时它们包含0xffffffff
或其他非零垃圾。
修复是这样掩饰它:
uint64_t val = ((hi & 0xffffffffULL) << 32) | (lo & 0xffffffffULL);
或者,如果我在分配期间屏蔽,似乎可以工作:
uint64_t lo = ((buf[3] << 24) | (buf[2] << 16) | (buf[1] << 8) | buf[0]) & 0xffffffff;
uint64_t hi = ((buf[7] << 24) | (buf[6] << 16) | (buf[5] << 8) | buf[4]) & 0xffffffff;
我想知道为什么这是必要的。我可以想到解释这一点是我的编译器直接在64位寄存器上进行lo
和hi
的所有移位和组合,我可能期望高阶32中的未定义行为-bits如果是这种情况。
有人可以证实我的怀疑或以其他方式解释这里发生的事情,并评论我的两种解决方案中哪些(如果有的话)更可取?
答案 0 :(得分:3)
如果您尝试转移char
或unsigned char
,请将自己置于标准整数促销的左右。你最好自己投射价值,之前你试图改变它们。如果你这样做,你就不必分开上半部分和上半部分。
inline double ReadLittleEndianDouble( const unsigned char *buf )
{
uint64_t val = ((uint64_t)buf[7] << 56) | ((uint64_t)buf[6] << 48) | ((uint64_t)buf[5] << 40) | ((uint64_t)buf[4] << 32) |
((uint64_t)buf[3] << 24) | ((uint64_t)buf[2] << 16) | ((uint64_t)buf[1] << 8) | (uint64_t)buf[0];
return *(double*)&val;
}
只有当CPU是big-endian或者缓冲区可能没有针对CPU架构正确对齐时,所有这一切都是必要的,否则你可以大大简化:
return *(double*)buf;