【问题标题】:endianness conversion, regardless of endianness字节序转换,与字节序无关
【发布时间】:2014-10-27 18:17:40
【问题描述】:

htonl()ntohl() 的许多实现首先测试平台的字节顺序,然后返回一个无操作或字节交换的函数。

我曾经在网上阅读过有关处理大/小端转换的一些技巧的网页,但对硬件配置没有任何先入为主的了解。只考虑字节顺序:内存中整数的表示。但我再也找不到了,所以我写了这个:

typedef union {
    uint8_t b[4];
    uint32_t i;
} swap32_T;

uint32_t to_big_endian(uint32_t x) {
    /* convert to big endian, whatever the endianness of the platform */
    swap32_T y;
    y.b[0] = (x & 0xFF000000) >> 24;
    y.b[1] = (x & 0x00FF0000) >> 16;
    y.b[2] = (x & 0x0000FF00) >> 8;
    y.b[3] = (x & 0x000000FF);
    return y.i;
}

我的两个问题是:

  • 您知道编写此to_big_endian() 函数的更简洁的方法吗?
  • 您是否曾经为这个我找不到的神秘页面添加了书签,其中包含关于字节序的非常宝贵(因为不寻常)的建议?

编辑

不是真正的重复(即使非常接近),主要是因为我确实 想要检测字节顺序。相同的代码在两种架构上编译,结果相同

小端序

  • 对于u = 0x12345678(存储为0x78 0x56 0x34 0x12
  • to_big_endian(u) = 0x12345678(存储为0x78 0x56 0x34 0x12

大端

  • 对于u = 0x12345678(存储为0x12 0x34 0x56 0x78
  • to_big_endian(u) = 0x78563412(存储为0x78 0x56 0x34 0x12

相同的代码,相同的结果...在内存中。

【问题讨论】:

标签: c endianness


【解决方案1】:

这是我自己的版本(尽管本例中的内存约定是小端而不是大端):

/* unoptimized version; solves endianess & alignment issues */
static U32 readLE32 (const BYTE* srcPtr)
{
    U32 value32 = srcPtr[0];
    value32 += (srcPtr[1]<<8);
    value32 += (srcPtr[2]<<16);
    value32 += (srcPtr[3]<<24);
    return value32;
}
static void writeLE32 (BYTE* dstPtr, U32 value32)
{
    dstPtr[0] = (BYTE)value32;
    dstPtr[1] = (BYTE)(value32 >> 8);
    dstPtr[2] = (BYTE)(value32 >> 16);
    dstPtr[3] = (BYTE)(value32 >> 24);
}

基本上,为了使代码更易于阅读,函数原型中缺少的是指向源内存或目标内存的指针。

【讨论】:

  • 不错的收获。唉,其他约束强加了今天的原型。但是明天... ;)
【解决方案2】:

根据您的意图,这可能是您问题的答案,也可能不是。但是,如果您只想将各种类型转换为各种字节序(包括 64 位类型和小字节序转换,htonl 显然不会这样做),您可能需要考虑 @987654322 @及相关函数:

   uint16_t htobe16(uint16_t host_16bits);
   uint16_t htole16(uint16_t host_16bits);
   uint16_t be16toh(uint16_t big_endian_16bits);
   uint16_t le16toh(uint16_t little_endian_16bits);

   uint32_t htobe32(uint32_t host_32bits);
   uint32_t htole32(uint32_t host_32bits);
   uint32_t be32toh(uint32_t big_endian_32bits);
   uint32_t le32toh(uint32_t little_endian_32bits);

   uint64_t htobe64(uint64_t host_64bits);
   uint64_t htole64(uint64_t host_64bits);
   uint64_t be64toh(uint64_t big_endian_64bits);
   uint64_t le64toh(uint64_t little_endian_64bits);

这些功能在技术上是非标准的,但它们似乎出现在大多数 Unices 上。

然而,也应该说,正如 Paul R 在 cmets 中正确指出的那样,没有字节序的运行时测试。字节序是给定 ABI 的固定特性,因此在编译时它始终是一个常数。

【讨论】:

    【解决方案3】:

    嗯...这当然是一个可行的解决方案,但我不明白你为什么要使用union。如果你想要一个字节数组,为什么不把一个字节数组作为输出指针参数呢?

    void uint32_to_big_endian(uint8_t *out, uint32_t x)
    {
      out[0] = (x >> 24) & 0xff;
      out[1] = (x >> 16) & 0xff;
      out[2] = (x >> 8)  & 0xff;
      out[3] = x & 0xff;
    }
    

    此外,在代码方面,先移位,后掩码通常更好。它需要更小的掩码文字,这通常对代码生成器更好。

    【讨论】:

    • 同意 :) 我让这个问题成为这个问题,并更正我的代码。我想我发现union 比指针转换更清晰......只是品味问题
    • @yota 我确实没有提到或推荐任何“指针转换”。我的意思是该函数可能看起来像void uint32_to_big_endian(uint8_t *out, uint32_t x);,只需将四个 8 位值写入out,以大端顺序表示x 的字节。
    【解决方案4】:

    嗯,这是我对通用有符号/无符号整数的解决方案,独立于机器字节序,并且可以存储数据的任何大小 --- 您需要为每个版本提供一个版本,但算法是相同的):

    AnyLargeEnoughInt fromBE(BYTE *p, size_t n)
    {
        AnyLargeEnoughInt res = 0;
        while (n--) {
            res <<= 8;
            res |= *p++;
        } /* for */
        return res;
    } /* net2host */
    
    void toBE(BYTE *p, size_t n, AnyLargeEnoughInt val)
    {
        p += n;
        while (n--) {
            *--p = val & 0xff;
            val >>= 8;
        } /* for */
    } /* host2net */
    
    AnyLargeEnoughInt fromLE(BYTE *p, size_t n)
    {
        p += n;
        AnyLargeEnoughInt res = 0;
        for (n--) {
            res <<= 8;
            res |= *--p;
        } /* for */
        return res;
    } /* net2host */
    
    void toLE(BYTE *p, size_t n, AnyLargeEnoughInt val)
    {
        while (n--) {
            *p++ = val & 0xff;
            val >>= 8;
        } /* for */
    } /* host2net */
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2022-01-22
      • 2013-08-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多