这是一个简单的函数,试图从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; }
不幸的是,这是未定义的行为,因为最后的<< =转移到符号位. 所以现在我们尝试以下方法:
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中调用实现定义的行为,从unsigned转换为signed.
如何在“明确定义”的领域中做到这一点?
解决方法
首先将字节组合成无符号值.除非您需要组装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×80}可能会产生未定义的行为[因为它会计算值-2147483648]但是代码应该以完全可移植的方式处理所有值,这些值将在“长”.