1

我试图将字节数组转换为长

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 

知道错误在哪里吗?

4

3 回答 3

3

这是因为类型提升和符号扩展。数组中的每个值char都是有符号的,位移是一个整数运算。当您使用移位运算符时,它的计算结果为 an int,并且因为您char的 s 是有符号的,所以移位它们会产生有符号int的 s。

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

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

char您可以做的另一件事是将每个与0xfflike逐位与运算((b[i] & 0xff) << 24)。与0xff将产生一个int,保持最低有效 8 位完整,左侧为零,没有符号扩展。

于 2015-03-20T17:19:53.473 回答
0

2件事:

  1. char可以是有符号或无符号的,因此不应用于存储字符以外的数据类型。

    在 C、C++ 和大多数类似 C 的语言中,任何比表达式中int 必须提升的类型更窄的类型,您的语句都将被这样处理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 填充。

    在 MSVCchar中,默认情况下是签名的。您可以使用/J使 char 无符号,这将解决您的部分问题。但随之而来的另一个问题是:

  2. 在 Windowslong中是32 位类型,因此您不能将 8 个字节装入其中。此外int,在大多数现代系统上也是 32 位的,并且在提升b[i]到 int移位超过 31 之后是未定义的行为,这就是您的程序所做的。

因此,要便携地解决所有问题,您需要:

  • 将所有b[i]转换为unsigned charor uint8_t,或通过与建议的 0xFF 进行与运算来屏蔽高位,如 0605002 建议的。或者只是将类型更改bunsigned char&*而不是char&*
  • 将返回类型更改为至少 64 位宽的类型,例如(unsigned) long long(u)int64_t(u)int_least64_t

结果可能如下所示

uint64_t 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]);
}

或者

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

但是,您实际上并不需要编写函数来反转字节序。已经有ntohll()并且htonll()为此目的

reversedEndian = ntohll(originalValue);

如果输入必须是char数组,则只需将值复制到uint64_t

memcpy(&originalValue, &b, sizeof originalValue);
reversedEndian = ntohll(originalValue);

您可以进一步减少整个事情reversedEndian = ntohll(*(int64_t*)&b);是否允许严格别名,因为在 x86 上通常允许未对齐访问

于 2015-03-23T12:37:55.323 回答
-1

需要考虑的几件事

  1. 包括cstdint并使用std::uint64_tstd::uint8_t为您键入,以便符号没有问题。
  2. 逻辑还取决于您的机器是小端还是大端。对于 Little Endian 机器,您需要先放置最低有效字节,然后再往上走。您的逻辑适用于 Big Endian。
  3. 您可能遇到计数溢出。更好的方法是显式声明 auint64_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;
}
于 2015-03-20T19:26:49.243 回答