字节顺序转换,无需依赖未定义的行为

我正在使用C读取.png图像文件,如果您不熟悉PNG编码格式,则有用的整数值将以以下形式编码在.png文件中4字节的大端整数。

我的计算机是一台低字节序的计算机,因此,将我用uint32_t从文件中读取的一个大字节序的fread()转换为我的计算机可以理解的一个低字节序的计算机,我已经一直在使用我写的这个小功能:

#include <stdint.h>

uint32_t convertEndian(uint32_t val){
  union{
    uint32_t value;
    char bytes[sizeof(uint32_t)];
  }in,out;
  in.value=val;
  for(int i=0;i<sizeof(uint32_t);++i)
    out.bytes[i]=in.bytes[sizeof(uint32_t)-1-i];
  return out.value;
}

这在我的x86_64 UNIX环境上可以很好地工作,gcc即使没有-Wall标志也可以编译而没有错误或警告,但是我相当有信心,我依赖于未定义的行为并对它进行类型校正在其他系统上可能无法正常工作。

我是否可以调用一个标准函数,该函数可以将big-endian整数可靠地转换为本机可以理解的整数,否则,是否存在另一种更安全的方式来进行此转换?

iCMS 回答:字节顺序转换,无需依赖未定义的行为

我在OP的代码中看不到真正的UB。

可移植性问题:是的。

“类型匹配在其他系统上可能无法正常工作”不是OP的C代码的问题,但可能会引起其他语言的麻烦。


但是要托管一个较大的(PNG)字节序怎么样?

按地址提取字节(具有MSByte的最低地址到具有LSByte的最高地址-“大”字节序),并通过移位后的字节形成结果。

类似的东西:

uint32_t Endian_BigToHost32(uint32_t val) {
  union {
    uint32_t u32;
    uint8_t u8[sizeof(uint32_t)]; // uint8_t insures a byte is 8 bits.
  } x = { .u32 = val };
  return 
      ((uint32_t)x.u8[0] << 24) |
      ((uint32_t)x.u8[1] << 16) |
      ((uint32_t)x.u8[2] <<  8) |
                 x.u8[3];
}

提示:许多库都具有特定于实现的功能来有效地解决此问题。示例be32toh

,

IMO最好是从字节读取为所需的格式,而不是明显地处理uint32_t然后在内部操作uint32_t。代码可能像这样:

uint32_t read_be32(uint8_t *src)   // must be unsigned input
{
     return (src[0] * 0x1000000u) + (src[1] * 0x10000u) + (src[2] * 0x100u) + src[3];
}

很容易将这种代码弄错,因此请确保您是从高级代表SO用户处获得的。但是,您可能经常会看到替代建议return (src[0] << 24) + (src[1] << 16) + (src[2] << 8) + src[3];,如果src[0] >= 128由于有符号整数溢出而导致uint8_t会导致不确定的行为,这是由于不幸的规则是整数促销将int带到有符号{{1 }}。由于移位大,还会在具有16位int的系统上导致不确定的行为。

现代编译器应该足够聪明地进行优化,例如the assembly produced by clang little-endian是:

read_be32:                              # @read_be32
    mov     eax,dword ptr [rdi]
    bswap   eax
    ret

但是我发现gcc 10.1产生了更复杂的代码,这似乎是一个令人惊讶的错过的优化错误。

,

此解决方案不依赖于访问联盟的非活动成员,而是依赖于无符号整数位移位操作,该操作可移植并安全地从big-endian转换为little-endian ,反之亦然 >

#include <stdint.h>

uint32_t convertEndian32(uint32_t in){
  return ((in&0xffu)<<24)|((in&0xff00u)<<8)|((in&0xff0000u)>>8)|((in&0xff000000u)>>24);
}
,

此代码独立于体系结构的字节序,从大字节序存储中的uint32_t指针中读取uchar_t。 (该代码就像读取了一个以256为底的数字一样)

uint32_t read_bigend_int(uchar_t *p,int sz)
{
    uint32_t result = 0;
    while(sz--) {
        result <<= 8;   /* multiply by base */
        result |= *p++; /* and add the next digit */
    }
}

如果您致电,例如:

int main()
{
    /* ... */
    uchar_t buff[1024];
    read(fd,buff,sizeof buff);

    uint32_t value = read_bigend_int(buff + offset,sizeof value);
    /* ... */
}
本文链接:https://www.f2er.com/2300005.html

大家都在问