字节序是什么

字节序是指在数据传输时,高位字节存储在内存中的较高位地址还是较低位地址。字节序分为大端字节序和小端字节序,两种字节序不能混用。

  • 大端字节序 :高位字节保存在较低位地址的内存中
  • 小端字节序 :高位字节保存在较高位地址的内存中

int n = 0x12345678; 为例,大端字节序内存中的存储的顺序为 0x12 0x34 0x56 0x78 ,小端字节序内存中的存储顺序为 0x78 0x56 0x34 0x12 。很明显可以看出大端字节序更符合人类的阅读习惯。

  • 常见的大端系统CPU:IBM z/Atchitecture
  • 常见的小端系统CPU:intel x86

网络字节序使用的大端字节序,常用网络协议如IPv4、IPv6、TCP和UDP协议都是使用大端字节序进行数据传输的。

提供字节序转换功能的函数接口

在网络通信接口中提供了转换主机字节序和网络字节序之间的函数接口,如下所示。

uint32_t htonl(uint32_t hostlong);
uint16_t htons(uint16_t hostshort);
uint32_t ntohl(uint32_t netlong);
uint16_t ntohs(uint16_t netshort);

有些时候,我们需要手动转换内存字节流,可以使用以下接口帮助实现指定字节序的转换。

小端字节序的函数实现如下:

// small endian
inline std::uint16_t SE_GetStreamU16(const void* p) {
    const std::uint8_t* pTemp = static_cast<const std::uint8_t*>(p);

    return std::uint16_t(pTemp[0] | (pTemp[1] << 8));
}

inline std::uint32_t SE_GetStreamU32(const void* p) {
    const std::uint8_t* pTemp = static_cast<const std::uint8_t*>(p);

    return std::uint32_t(pTemp[0]
			 | (pTemp[1] << 8)
			 | (pTemp[2] << 16)
			 | (pTemp[3] << 24));
}

inline void SE_SetStreamU16(void* p, std::uint16_t n) {
    std::uint8_t* pTemp = static_cast<std::uint8_t*>(p);

    pTemp[0] = std::uint8_t(n);
    pTemp[1] = std::uint8_t(n >> 8);
}

inline void SE_SetStreamU32(void* p, std::uint32_t n) {
    std::uint8_t* pTemp = static_cast<std::uint8_t*>(p);

    pTemp[0] = std::uint8_t(n);
    pTemp[1] = std::uint8_t(n >> 8);
    pTemp[2] = std::uint8_t(n >> 16);
    pTemp[3] = std::uint8_t(n >> 24);
}

大端字节序的函数实现如下:

// big endian
inline std::uint16_t BE_GetStreamU16(const void* p) {
    const std::uint8_t* pTemp = static_cast<const std::uint8_t*>(p);

    return std::uint16_t(pTemp[1] | pTemp[0] << 8);
}

inline std::uint32_t BE_GetStreamU32(const void* p) {
    const std::uint8_t* pTemp = static_cast<const std::uint8_t*>(p);

    return std::uint32_t(pTemp[3]
			 | (pTemp[2] << 8)
			 | (pTemp[1] << 16)
			 | (pTemp[0] << 24));
}

inline void BE_SetStreamU16(void* p, std::uint16_t n) {
    std::uint8_t* pTemp = static_cast<std::uint8_t*>(p);

    pTemp[0] = std::uint8_t(n >> 8);
    pTemp[1] = std::uint8_t(n);
}

inline void BE_SetStreamU32(void* p, std::uint32_t n) {
    std::uint8_t* pTemp = static_cast<std::uint8_t*>(p);

    pTemp[0] = std::uint8_t(n >> 24);
    pTemp[1] = std::uint8_t(n >> 16);
    pTemp[2] = std::uint8_t(n >> 8);
    pTemp[3] = std::uint8_t(n);
}

查看当前系统的字节序

通过 lscpu 命令,能得到cpu的大端和小端信息。

lscpu | grep -i endian

通过常用shell命令,在大端系统中会输出0,在小端系统中会输出1。

echo -n I | od -to2 | head -n1 | cut -f2 -d" " | cut -c6

借助python进行判断。

python -c "import sys; print(sys.byteorder)"
# or
python -c "import sys;sys.exit(0 if sys.byteorder=='big' else 1)"

借助linux的ELF进行判断,查看第6个字节,小端系统中为1,大端系统中为2。

xxd -c 1 -l 6 /bin/bash
# or
hexdump -n 6 -C /bin/bash

可以使用union类型验证字节序,以下示例代码在不同的字节序下会有不同的输出。

#include <stdio.h>
#include <inttypes.h>

typedef union un {
    int32_t x;
    char ch[4];
} un;

int main() {
    un u;
    u.ch[0] = 1;
    u.ch[1] = 2;
    u.ch[2] = 3;
    u.ch[3] = 4;
    printf("u.x=0x%x\n", u.x);
    return 0;
}