字节序(Endianness)

字节序(Endianness)是多字节数据的每一个字节在内存中存储的顺序。

字节序分为大端字节序(big-endian)和小端字节序(little-endian)。大端字节序表示数据的高位存储在内存的低地址,数据的低位存储在内存的高地址。而小端字节序则相反,即数据的高位存储在内存的高地址,数据的低位存储在内存的低地址。

举个例子,现在有一个两字节的数 0x1234,可以知道 12 是这个数据的十六进制的最高位,34 是这个数据的十六进制的最低位。所以将这个数据存储在内存中,假如按照大端字节序来存,则这个数据所在地址的第一个字节会存储 0x12,第二个字节会存储 0x34。假如按照小端字节序来存,则这个数据所在地址的第一个字节会存储 0x34 ,第二个字节会存储 0x12。如下如图所示:

那么为什么会有两种字节序呢?从 CPU 的角度来说,CPU 读取内存通常是从低地址往高地址顺序读取,而基础运算例如加法同样是先算最低位,然后依次向高位计算,这样如果将数据的低位存储在内存低地址上,就可以让 CPU 更加高效的计算。所以现在常用的 CPU 使用的都是小端字节序。而大端字节序的好处是更加符合人的阅读书写习惯,所以在其他一些场景例如网络数据传输中广泛使用大端字节序。

网络字节序

在网络协议中也会有多字节数据,例如 IP 地址还有端口号之类的,例如下图的 IP 协议头部:

这里的源 IP 地址占了四个字节,那么如何将一个常见形式的 IP 地址例如 192.168.1.2 存储进这四个字节的空间中呢,这就需要考虑网络字节序了。TCP/IP 协议簇规定,所有网络协议中的多字节数据均以大端字节序存储,即网络字节序为大端字节序。这样,上面的 IP 头部中的源 IP 地址的第一个字节存储 192,第二个字节存储 168 ,以此类推。

可以想到,通用的网络字节序不受操作系统等任何其他因素的影响,即使通信的两端使用的主机字节序不同,TCP/IP 协议栈也能正确的将网络中收到的数据包解析。

Linux 提供的 socket API 并不会帮我们将主机字节序转换成网络字节序,所以在调用这些 API 的时候得先自己把端口号等数据转换成网络字节序。幸好 C 标准库提供了一系列的库函数帮我们做这些事。

#include <arpa/inet.h>

uint32_t htonl(uint32_t hostlong);      //把uint32_t类型从主机序转换到网络序
uint16_t htons(uint16_t hostshort);     //把uint16_t类型从主机序转换到网络序
uint32_t ntohl(uint32_t netlong);       //把uint32_t类型从网络序转换到主机序
uint16_t ntohs(uint16_t netshort);      //把uint16_t类型从网络序转换到主机序
Last modification:October 27th, 2020 at 01:20 am