深入理解计算机系统 大端还是小端?
文章目录
字节顺序(Endianness)
在计算机科学领域中,指存储器中或在数字通信链路中,组成多字节的字(例如 int32 四字节,short 两字节)的字节的排列顺序。
而我们常说的大端小端模式是计算机中 字节顺序(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的定义如下:
- Little-Endian就是低位字节排放在内存的低地址端,高位字节排放在内存的高地址端。
- Big-Endian就是高位字节排放在内存的低地址端,低位字节排放在内存的高地址端。
0x1234567 的大端字节序和小端字节序的写法如下图。
如图所示,大端更像是人类的读写数值的方式,那为什么还有小端模式?
为什么会有小端字节序?
计算机电路先处理低位字节,效率比较高(参考 & 运算),因为计算都是从低位开始的。所以,计算机的内部处理都是小端字节序。但是,人类还是习惯读写大端字节序。所以,除了计算机的内部处理,其他的场合几乎都是大端字节序,比如网络传输和文件储存。
什么时需要区分字节序
计算机处理字节序的时候,不知道什么是高位字节,什么是低位字节。它只知道按顺序读取字节,先读第一个字节,再读第二个字节。
只有读取的时候,才必须区分字节序,其他情况都不用考虑。
处理器读取外部数据(例如: 内存中的数据)的时候,必须知道数据的字节序,将其转成正确的值。然后,就正常使用这个值,完全不用再考虑字节序。
即使是向外部设备写入数据,也不用考虑字节序,正常写入一个值即可。外部设备会自己处理字节序的问题。
例如 处理器读入一个 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") } } |