看了一个视频,Endianness Explained1, 对 Endianness 介绍的非常好。尤其是对常见的两个误解的解释。
Endian 的概念是 1980 年的4月1日 Danny Cohen 在 On Holy Wars and a Plea for Peace2 提出来的。名字的来源是《格列佛游记》小说中有两派人由于「打鸡蛋打哪头」意见不同而打架。鸡蛋的端,叫做 end,end-ian 就是这么来的。
在计算机的世界中,最小可以寻址的单位通常是 byte,1 个 byte 是 8 bits,这个没有争议。
如果一个数据类型需要占用多个 bytes,比如 32 位的 int,占用 4 个 bytes,那我们在编程的时候会用一个内存地址表示这个 int,从这个地址开始,后面一共 4 个 bytes,来表示一个 int。比如一个 16 进制表示的 int 值:0x01020304
,其中 01
是 most significant bit,是重要的数字,因为它决定了整个数字的数量级是多大,可以把 01 叫做大头,如果把大头放在前面,即,表示成 0x01020304
,这就是 Big Endian. 如果把大头放在这 4 个 bytes 的最后,即,表示成 0x04030201
,那就是 Little Endian.
一般来说,这对于程序员是透明的,因为处理 endian 的是 CPU,把数据写入内存的是 CPU,从内存取出来的还是 CPU。所以不管 CPU 是 Little Endian 还是 Big Endian,只要它始终保持一致,就可以了。
但是如果涉及到网络的序列化(其实不仅仅是网络,数据只要离开本机,就涉及 endianness),问题就来了。一个 CPU 序列化成 Little Endian,通过网络传输给另一个机器,另一个机器的 CPU 是 Big Endian,那不就乱套了吗?(现代的大部分 CPU 是 Little Endian, 网络使用 Network Byte order,是 Big Endian。)
所以这里就需要一个转换,libc 提供了两个转换函数:
htons
: Host byte order 转换成 Network byte orderntohs
: Network byte order 转换成 Host byte order
两个误解:
- 「Endian 是 bit 级别的。」这是错误的,Endian 是 byte 级别的。假如一个数据格式只有 8 个 bit,那么无论是 Little Endian 还是 Big Endian,这 8 个 bit 的表示方法都不会变。也没有 Endian 转换的必要。(有些情况下,同一个 byte 内,编译器也会考虑 endianness,见评论2)
- 另一个需要注意的点,是 Endian 只涉及多个 byte 组成的数据结构。
char
array 这种不算,假设一个 char array 是 “abcd”,那么它无论在哪里都是 “abcd”。Endian 的问题只发生在用一个地址表示多个字节,而 array 中,每一个元素都可以被寻址。在序列化的时候,是对 4 个元素分别序列化。
- https://www.youtube.com/watch?v=LxvFb63OOs8 ↩︎
- ON HOLY WARS AND A PLEA FOR PEACE https://www.rfc-editor.org/ien/ien137.txt ↩︎
并不一定是网络。只要涉及通信就会有这个问题。每个报头里的整型数据进行拷贝或者赋值的时候都要考虑。
您说的对,稍微修改了一下原文,感谢。
有一点”Endian 是 byte 级别的” 其实不准确吧, 譬如 struct iphdr中的__LITTLE_ENDIAN_BITFIELD和__BIG_ENDIAN_BITFIELD.
不过我觉得到byte级别就行了, 再往下深究意义不大, 因为现在没有人会故意大改现有的相关硬件设计.这个问题不能深究, 要不挺绕的.
感谢评论,确实从来没想到这种同一个 byte 内区分高位4 bits 和低位的问题。我稍微在原文补充一下。