无论字节顺序如何,字节顺序转换

时间:2014-09-03 09:42:04

标签: c endianness

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

我曾经在网上阅读了一些关于处理大/小端转换的技巧的页面,没有任何关于硬件配置的先入为主的知识。只是为了它的结尾采用endianness:在记忆中表示整数。但我再也找不到了,所以我写了这个:

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

big endian

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

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

4 个答案:

答案 0 :(得分:3)

这是我自己的相同版本(虽然这个例子中的内存约定是小端而不是大端):

/* 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);
}

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

答案 1 :(得分:2)

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

   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在评论中正确指出的那样,没有对字节序的运行时测试。字节序是给定ABI的固定特征,因此它在编译时始终是常量。

答案 2 :(得分:1)

嗯......这当然是一个可行的解决方案,但我不明白你为什么要使用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;
}

此外,首先转移代码通常更好,然后再屏蔽。它需要更小的掩码文字,这通常对代码生成器更好。

答案 3 :(得分:0)

嗯,这是我的一般签名/无符号整数的解决方案,与机器字节序无关,并且能够存储数据的任何大小 - 你需要一个版本,但算法是相同的):

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 */