字节序

多字节数据的字节顺序。分为大端序和小端序两种。

字节序 (Endianness) 是指多字节数据存入内存或文件时的字节排列顺序。先存放高位字节的是大端序 (BE),先存放低位字节的是小端序 (LE)。这个术语出自乔纳森·斯威夫特的小说《格列佛游记》,书中有一则为鸡蛋该从哪一端敲开而争执的寓言。

用具体例子来说明。把十六进制的 0x1234 存成 2 个字节时,大端序在内存中按 12 34 的顺序排列,小端序按 34 12 的顺序排列。对人来说大端序更直观,但在加法之类的处理中,小端序有时效率更高。

各类处理器采用的字节序方针并不相同。Intel 的 x86/x64 处理器采用小端序,2026 年时的桌面 PC 与服务器绝大多数都是这种方式。而网络协议 (TCP/IP) 则以大端序 (网络字节序) 为标准。ARM 处理器是双端序 (两种都支持),可以通过操作系统或固件的设置切换。Apple Silicon (M1 及以后) 以小端序模式运行。

在字符编码中,字节序对 UTF-16 和 UTF-32 尤其重要。例如「あ」(U+3042) 在大端序下是 30 42,在小端序下是 42 30,字节序列会整个颠倒,因此字节序不一致会直接成为乱码的原因。为应对这个问题,人们使用 BOM (Byte Order Mark, U+FEFF)。不明示字节序而以「UTF-16」的名义交换数据时,可以根据文件开头来判别:FE FF 是大端序,FF FE 是小端序。UTF-32 的 BOM 是 4 个字节,大端序为 00 00 FE FF,小端序为 FF FE 00 00。

这里容易被忽略的是,像 UTF-16BE、UTF-16LE 这样带有明示字节序的标签时该如何处理。按 Unicode 的规定,这种情况下 BOM 不仅没有必要,而且不得使用;即使开头出现 U+FEFF,它也不再是顺序的指示,而是被解释为 ZWNBSP (零宽不换行空格) 这个实际存在的字符。把「UTF-16BE 的 BOM 是 FE FF」当作记忆口诀并不准确,正确的理解方式是:同样的字节序列,含义会随着交给读取方的标签而改变。另外 UTF-32LE 的 BOM 开头 2 个字节是 FF FE,与 UTF-16LE 的 BOM 一致,所以只看开头 2 个字节的简易判别会把 UTF-32LE 误认成 UTF-16LE。

常见的陷阱是,在字节序不同的系统之间交换二进制数据时忘了做转换。网络编程中需要使用 htons() (host to network short) 和 ntohl() (network to host long) 这类转换函数,显式地转换字节顺序。省掉这一步转换,数值数据就得不到正确的解释,会成为难以调试的 bug 的原因。

从字符计数的角度看,字节序影响字符的字节表示,但字符数本身不会改变。不过字节数的计数需要把字节序和有无 BOM 都考虑进去。UTF-16 文件带有 BOM 时,开头的 2 个字节不是正文内容而是元信息,因此要算出准确的字节数,把 BOM 排除在计算之外才合适。字符数也会受到前面提到的标签处理方式的影响。FE FF 30 42 30 44 这 6 个字节,若按「UTF-16」读取就是「あい」这 2 个字符;同样的字节序列按 UTF-16BE 读取,开头会被视为 ZWNBSP,于是变成 3 个字符。计数结果比预想多出正好 1 个字符时,可以怀疑 BOM 没有作为元信息被剔除,而是作为不可见字符混进了正文,这样更容易接近原因。

分享这篇文章