字节顺序(Endianness)

wiki

在计算机科学领域中,指存储器中或在数字通信链路中,组成多字节的字(例如 int32 四字节,short 两字节)的字节的排列顺序。

endianness

而我们常说的大端小端模式是计算机中 字节顺序(Endianness) 常见的两种表现形式。(此外还有混合序 (Middle-Endian))

端(endian) 的起源

“endian”一词来源于十八世纪爱尔兰作家乔纳森·斯威夫特(Jonathan Swift)的小说《格列佛游记》(Gulliver's Travels)。小说中,小人国为水煮蛋该从大的一端(Big-End)剥开还是小的一端(Little-End)剥开而争论,争论的双方分别被称为“大端派”和“小端派”。以下是1726年关于大小端之争历史的描述:

我下面要告诉你的是,Lilliput和Blefuscu这两大强国在过去36个月里一直在苦战。战争开始是由于以下的原因:我们大家都认为,吃鸡蛋前,原始的方法是打破鸡蛋较大的一端,可是当今皇帝的祖父小时候吃鸡蛋,一次按古法打鸡蛋时碰巧将一个手指弄破了。因此他的父亲,当时的皇帝,就下了一道敕令,命令全体臣民吃鸡蛋时打破鸡蛋较小的一端,违令者重罚。老百姓们对这项命令极其反感。历史告诉我们,由此曾经发生过6次叛乱,其中一个皇帝送了命,另一个丢了王位。这些叛乱大多都是由Blefuscu的国王大臣们煽动起来的。叛乱平息后,流亡的人总是逃到那个帝国去寻求避难。据估计,先后几次有11000人情愿受死也不肯去打破鸡蛋较小的一端。关于这一争端,曾出版过几百本大部著作,不过大端派的书一直是受禁的,法律也规定该派任何人不得做官。

大端 还是 小端

Big-Endian和Little-Endian的定义如下:

  1. Little-Endian就是低位字节排放在内存的低地址端,高位字节排放在内存的高地址端。
  2. Big-Endian就是高位字节排放在内存的低地址端,低位字节排放在内存的高地址端。

0x1234567 的大端字节序和小端字节序的写法如下图。

endian

如图所示,大端更像是人类的读写数值的方式,那为什么还有小端模式?

为什么会有小端字节序?

计算机电路先处理低位字节,效率比较高(参考 & 运算),因为计算都是从低位开始的。所以,计算机的内部处理都是小端字节序。但是,人类还是习惯读写大端字节序。所以,除了计算机的内部处理,其他的场合几乎都是大端字节序,比如网络传输和文件储存。

什么时需要区分字节序

计算机处理字节序的时候,不知道什么是高位字节,什么是低位字节。它只知道按顺序读取字节,先读第一个字节,再读第二个字节。

只有读取的时候,才必须区分字节序,其他情况都不用考虑。

处理器读取外部数据(例如: 内存中的数据)的时候,必须知道数据的字节序,将其转成正确的值。然后,就正常使用这个值,完全不用再考虑字节序。
即使是向外部设备写入数据,也不用考虑字节序,正常写入一个值即可。外部设备会自己处理字节序的问题。
例如 处理器读入一个 short 2 字节的整数,

如果是大端字节序 x = buf[offset] * 256 + buf[offset + 1];

如果是小端字节序 x = buf[offset] + 256 * buf[offset + 1];

上面代码中,buf是整个数据块在内存中的起始地址,offset是当前正在读取的位置。第一个字节乘以256,再加上第二个字节,就是大端字节序的值。

判断当前计算机的字节序

c 实现

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// 强制类型转换实现
int byteOrder() {
    short a = 0x1234;
    char b = (char) a // 强制类型转换取 a 的低 8 位
    if (b == 0x12) {
        return 1 // big endian
    } else if (b == 0x34){
        return 2 // little endian
    } else {
        return -1 // unkown type
    }
}

// 利用了union各字段共享内存的特性:
int byteOrder() {
    union {
        short value;
        char bytes[2];
    } u;

    u.value = 0x0102;

    if (u.bytes[0] == 1 && u.bytes[1] == 2) {
        return 1; // big endian
    } else if (u.bytes[0] == 2 && u.bytes[1] == 1) {
        return 2; // little endian
    } else {
        return -1; // unknown
    }
}

golang 实现

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
func endian() {
	a := int32(0x01020304)
	u := unsafe.Pointer(&a)
	pb := (*byte)(u)
	c := *pb

	if c == 0x04{
		fmt.Println("little endian")
	} else {
		fmt.Println("big endian")
	}
}

参考

理解字节序

wiki 定义

深入理解计算机系统 - 信息存储