这是一个简单的函数,它试图从大端缓冲区中读取一个通用的二进制补码整数,我们假设 std::is_signed_v<INT_T>
。 :
template<typename INT_T>
INT_T read_big_endian(uint8_t const *data) {
INT_T result = 0;
for (size_t i = 0; i < sizeof(INT_T); i++) {
result <<= 8;
result |= *data;
data++;
}
return result;
}
不幸的是,这是未定义的行为,因为最后<<=
移入符号位。
所以现在我们尝试以下操作:
template<typename INT_T>
INT_T read_big_endian(uint8_t const *data) {
std::make_unsigned_t<INT_T> result = 0;
for (size_t i = 0; i < sizeof(INT_T); i++) {
result <<= 8;
result |= *data;
data++;
}
return static_cast<INT_T>(result);
}
但我们现在在 static_cast
中调用实现定义的行为, 从无符号转换为有符号。
如何在保持“明确定义”的范围内做到这一点?
最佳答案
首先将字节组装成一个无符号值。除非您需要组装 9 个或更多八位字节的组,否则保证符合 C99 的实现具有足够大的类型来容纳它们(C89 实现将保证具有足够大的无符号类型以容纳至少四个).
在大多数情况下,如果您想将一个八位位组序列转换为一个数字,您就会知道您需要多少个八位位组。如果数据被编码为 4 个字节,无论 int
和 long
的大小如何,您都应该使用四个字节(可移植函数应该返回类型 long
).
unsigned long octets_to_unsigned32_little_endian(unsigned char *p)
{
return p[0] |
((unsigned)p[1]<<8) |
((unsigned long)p[2]<<16) |
((unsigned long)p[3]<<24);
}
long octets_to_signed32_little_endian(unsigned char *p)
{
unsigned long as_unsigned = octets_to_unsigned32_little_endian(p);
if (as_unsigned < 0x80000000)
return as_unsigned;
else
return (long)(as_unsigned^0x80000000UL)-0x40000000L-0x40000000L;
}
请注意,减法分两部分进行,每部分都在一个带符号的长整数范围内,以允许系统中 LNG_MIN
为 -2147483647 的可能性。尝试在这样的系统上转换字节序列 {0,0,0,0x80} 可能会产生未定义的行为 [因为它会计算值 -2147483648] 但代码应该以完全可移植的方式处理所有值,这些值将在“长”。
关于c++ - 如何在不调用未定义或实现定义的行为的情况下从 uint8_t 的缓冲区中读取有符号整数?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/46700362/