char数组为long导致意外值

时间:2015-03-20 17:09:22

标签: c++ bit-manipulation bit-shift sign-extension

我尝试将字节数组转换为long

long readAndSkipLong(char*& b)
{
    unsigned long ret = (b[0] << 56) | (b[1] << 48) | (b[2] << 40) | (b[3]<<32) | (b[4] << 24) | (b[5] << 16) | (b[6] << 8) | (b[7]);
    return ret;
}

我的转变似乎不对。对于预期值

152  --> 00000000 00000000 00000000 00000000 00000000 00000000 00000000 10011000

我明白了:

-104  --> 11111111 11111111 11111111 11111111 11111111 11111111 11111111 10011000 

知道bug在哪里?

3 个答案:

答案 0 :(得分:4)

这是因为类型促销和签名扩展。 char数组中的每个值都是有符号的,并且位移是一个整数运算。当您使用移位运算符时,它会计算为int,并且由于您的char已签名,因此移位它们会生成签名的int

最后一个(最右边)字节有1作为符号位。升级为int后,其值将通过符号扩展名变为-104。在对其余数字进行OR运算时,所有1位都不受影响。

要避免此问题,您可以在移位和ORing之前将每个char转换为unsigned long

您可以做的另一件事是将char0xff((b[i] & 0xff) << 24)进行逐位AND运算。与0xff进行AND运算会生成int,保持最低有效8位保持不变,并向左零,无符号扩展。

答案 1 :(得分:0)

2件事:

  1. char can be signed or unsigned,因此不应该用于存储除字符以外的数据类型。

    在C,C ++和大多数类C语言中,任何类型都比表达式中的int must be promoted to int窄,并且您的语句将被视为

    unsigned long ret = ((int)b[0] << 56) | ((int)b[1] << 48)
                      | ((int)b[2] << 40) | ((int)b[3] << 32)
                      | ((int)b[4] << 24) | ((int)b[5] << 16)
                      | ((int)b[6] <<  8) | ((int)b[7]);
    

    如果char已签名,则使用签名扩展程序将其提升为int。因此,如果字节值为负,则顶部位将填充1秒。

    在MSVC中,char默认为已签名。您可以使用/J使char无符号,这将解决您的部分问题。但随后出现了另一个问题:

  2. 在Windows long is a 32-bit type中,您无法将8个字节打包到其中。此外,int在大多数现代系统上也是32位,并且在将b[i]提升为int shifting more than 31 is undefined behavior后,这是您的程序所做的。

  3. 因此,要解决所有需要解决的问题:

    • 将所有b[i]转换为unsigned charuint8_t,或通过将其与0xFF进行AND运算来屏蔽高位,如0605002建议的那样。或者只是将b的类型更改为unsigned char&*而不是char&*
    • 将ret更改为至少64位类型,例如long longint64_tint_least64_t

    结果可能如下所示

    long long readAndSkipLong(unsigned char*& b)
    {
        return ((uint64_t)b[0] << 56) | ((uint64_t)b[1] << 48)
             | ((uint64_t)b[2] << 40) | ((uint64_t)b[3] << 32)
             | ((uint64_t)b[4] << 24) | ((uint64_t)b[5] << 16)
             | ((uint64_t)b[6] <<  8) | ((uint64_t)b[7]);
    }
    

    但是,在x86上通常不允许进行未对齐访问,因此您只需用

    替换该功能即可
    ntohll(*(int64_t*)&b);
    

答案 2 :(得分:-1)

要考虑的事情 -

  1. 包含cstdint并使用std :: uint64_t和std :: uint8_t为你输入类型,这样就不会出现符号问题。
  2. 逻辑还取决于您的机器是小端还是大端。对于Little Endian机器,您需要先将最低有效字节放在第一位,然后再放高。你的逻辑是Big Endian。
  3. 你可能有计数溢出。更好的方法是明确声明uint64_t并使用它。
  4. 这是我在一个小端机器上为字节写入uint64_t的一些代码。

    std::uint64_t bytesToUint64(std::uint8_t* b) {
        std::uint64_t msb = 0x0u;
        for (int i(0); i < 7; i++) {
            msb |= b[i];
            msb <<= 8;
        }
        msb |= b[7];
    
        return msb;
    }
    

    OP编辑(实施提示1):

    long readAndSkipLong(char*& b)
    {
        std::uint64_t ret = 
            ((std::uint8_t)b[0] << 56) | 
            ((std::uint8_t)b[1] << 48) | 
            ((std::uint8_t)b[2] << 40) | 
            ((std::uint8_t)b[3] << 32) | 
            ((std::uint8_t)b[4] << 24) | 
            ((std::uint8_t)b[5] << 16) | 
            ((std::uint8_t)b[6] <<  8) | 
            ((std::uint8_t)b[7]);
        b+=8;
    
        return ret;
    }