Eri*_*ric 7 c++ undefined-behavior implementation-defined-behavior
这是一个简单的函数,试图从big-endian缓冲区读取一个通用的二进制补码整数,我们假设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;
}
Run Code Online (Sandbox Code Playgroud)
不幸的是,这是未定义的行为,因为最后一个<<=转移到符号位.
所以现在我们尝试以下方法:
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);
}
Run Code Online (Sandbox Code Playgroud)
但我们现在正在调用实现定义的行为static_cast,从unsigned转换为signed.
如何在"明确定义"的领域中做到这一点?
首先将字节组装成无符号值。除非您需要组装 9 个或更多八位字节的组,否则保证一致的 C99 实现具有足够大的类型来容纳所有这些八位字节(C89 实现将保证具有足够大的无符号类型以容纳至少四个)。
在大多数情况下,当您想要将八位位组序列转换为数字时,您会知道需要多少个八位位组。int如果数据编码为 4 个字节,则无论和的大小如何,都应使用四个字节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;
}
Run Code Online (Sandbox Code Playgroud)
请注意,减法是作为两部分完成的,每部分都在有符号长整型的范围内,以允许系统中LNG_MINis -2147483647 的可能性。尝试在此类系统上转换字节序列 {0,0,0,0x80} 可能会产生未定义的行为 [因为它将计算值 -2147483648],但代码应以完全可移植的方式处理在以下范围内的所有值“长的”。