我尝试将字节数组转换为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在哪里?
答案 0 :(得分:4)
这是因为类型促销和签名扩展。 char
数组中的每个值都是有符号的,并且位移是一个整数运算。当您使用移位运算符时,它会计算为int
,并且由于您的char
已签名,因此移位它们会生成签名的int
。
最后一个(最右边)字节有1
作为符号位。升级为int
后,其值将通过符号扩展名变为-104
。在对其余数字进行OR运算时,所有1
位都不受影响。
要避免此问题,您可以在移位和ORing之前将每个char
转换为unsigned long
。
您可以做的另一件事是将char
与0xff
按((b[i] & 0xff) << 24)
进行逐位AND运算。与0xff
进行AND运算会生成int
,保持最低有效8位保持不变,并向左零,无符号扩展。
答案 1 :(得分:0)
2件事:
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无符号,这将解决您的部分问题。但随后出现了另一个问题:
在Windows long
is a 32-bit type中,您无法将8个字节打包到其中。此外,int
在大多数现代系统上也是32位,并且在将b[i]
提升为int shifting more than 31 is undefined behavior后,这是您的程序所做的。
因此,要解决所有需要解决的问题:
b[i]
转换为unsigned char
或uint8_t
,或通过将其与0xFF进行AND运算来屏蔽高位,如0605002建议的那样。或者只是将b
的类型更改为unsigned char&*
而不是char&*
long long
,int64_t
或int_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)
要考虑的事情 -
这是我在一个小端机器上为字节写入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;
}