移位和屏蔽32位值时未定义的uint64_t高位

时间:2014-09-18 01:16:43

标签: c++

我在看似无害的函数中有一些未定义的行为,它从缓冲区解析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位变量lohi中,我合理地期望这些变量的高位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位寄存器上进行lohi的所有移位和组合,我可能期望高阶32中的未定义行为-bits如果是这种情况。

有人可以证实我的怀疑或以其他方式解释这里发生的事情,并评论我的两种解决方案中哪些(如果有的话)更可取?

1 个答案:

答案 0 :(得分:3)

如果您尝试转移charunsigned 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;