char数组为long导致意外值

问题描述 投票:1回答:3

我试图将一个字节数组转换为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在哪里?

c++ bit-manipulation bit-shift sign-extension
3个回答
4
投票

这是因为类型促销和标志扩展。 char数组中的每个值都是有符号的,并且位移是一个整数运算。当你使用移位运算符时,它会计算为int,并且因为你的chars是有符号的,所以移位它们会产生签名的ints。

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

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

你可以做的另一件事就是用char0xff对每个((b[i] & 0xff) << 24)进行逐位AND运算。与0xff进行AND运会产生一个int,保持最不重要的8位完整并且向左零,没有符号扩展。


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之后,这是你的程序所做的。

因此,为了便于解决所有问题,您需要:

  • 将所有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);

-1
投票

有几件事要考虑 -

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

这是我在一个小端机器上为字节写入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;
}
© www.soinside.com 2019 - 2024. All rights reserved.