c++ - 如何在不调用未定义或实现定义的行为的情况下从 uint8_t 的缓冲区中读取有符号整数?

标签 c++ undefined-behavior implementation-defined-behavior

这是一个简单的函数,它试图从大端缓冲区中读取一个通用的二进制补码整数,我们假设 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 个字节,无论 intlong 的大小如何,您都应该使用四个字节(可移植函数应该返回类型 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/

相关文章:

c++ - WinDbg SRV* 的文档有误吗?

c++ - 未定义的行为或 gcc 优化错误

c++ - 通过指向类的第一个成员的指针访问成员是否是未定义的行为

c - 有符号整数溢出是否定义了未定义的行为或实现?

c - 字节中的位数 - C 标准

使用双三次方法的 C++ 图像插值

c# - C# 如何在 C++ 不允许虚拟模板方法的情况下允许虚拟泛型方法?

c# - 如何在 C# 中编码数据类型 unsigned char**?

c - 为什么这些构造使用增量前和增量后未定义的行为?

c++ - 未定义、未指定和实现定义的行为