字节序探析:大端与小端的比较

作者: 阮一峰

日期: 2022年6月 3日

今天谈谈一个重要的计算机概念,大家可能都听说过它,但是很少深究,那就是字节序(Endianness)。

一、概念

字节序指的是,多字节数据的内存排列顺序。这样说比较抽象,使用图形解释就很好懂。

内存好比一排房间,每个字节是一间房。每间房都有门牌号(内存地址),从0号开始,然后是1号、2号......

0号字节的地址小,称为低位内存;3号字节的地址大,称为高位内存。

现在有一个数值abcd要放进这些房间,每个房间放一个数字,那么有两种放法。

第一种放法是,第一位a放在低位地址(0号),最后一位d放在高位地址(3号)。

这种排列称为"大端序"(big-endian,简称 BE),即大头在前,因为aabcd的大头(最重要的数字)。

第二种放法是,第一位a放在高位地址(3号地址),最后一位d放在低位地址(0号地址)。

这种排列称为"小端序"(little-endian,简称 LE),即小头d在前。

大端序和小端序合称字节序,这两个名字来自18世纪的英国小说《格列佛游记》。某国分成两派,一派认为鸡蛋应该从大头吃起,称为"大端派";另一派认为,鸡蛋应该从小头吃起,称为"小端派"。两派相执不下,谁也无法说服谁,最后甚至为此交战。

二、可读性

对于人类来说,不同字节序的可读性是不一样的。大部分国家的阅读习惯是从左到右阅读。

大端序的最高位在左边,最低位在右边,符合阅读习惯。所以,对于这些国家的人来说,从左到右的大端序的可读性更好。

但是现实中,从右到左的小端序虽然可读性差,但应用更广泛,x86 和 ARM 这两种 CPU 架构都采用小端序,这是为什么?

或者换一种问法,两种不同的字节序为什么会并存,统一规定只使用一种,难道不是更方便吗?

原因是它们有各自的适用场景,某些场景大端序有优势,另一些场景小端序有优势,下面就逐一分析。

三、检查奇偶性

小端序优势最明显的,大概就是检查奇偶性,即通过查看个位数,确定某个数字是奇数还是偶数。

123456为例,大端序从左到右排列,计算机必须一直读到最后一位的个位数6,才能确定这是偶数。

小端序是从右到左排列,个位数在第一位。所以,只要读取第一位,就能确定它是偶数。

四、检查正负号

一个类似的场景是检查正负号,确定一个数是正数还是负数。

大端序的符号位在左边第一位,小端序的符号位在右边最后一位。所以,大端序有优势,只看第一位就能知道是不是负数。

五、比较大小

下一个操作是比较大小。现在有三个数字,需要比较大小:43662576,594,2。

上图是大端序排列,因为是从左到右排列,所以三个数字在右边个位数对齐。比较大小时,计算机就不得不读取每一个数的所有位,直到个位数,再进行比较。

如果改成小端序,就是下面的排列方式。

小端序是从右到左,所以三个数字在第一位对齐。计算机就不需要读取所有位,哪个数字先读不到下一位,就是最小的。比如,2这个数字就没有第二位,所以读到第二位时,就知道它是最小的。

所以,比较大小时,小端序有优势。

六、乘法

接下来,再看乘法操作。

乘法是逐位相乘,每一轮乘法都要向前进位。

上图是大端序的24165乘以3841。大端序的乘法是向左进位,也就是向左边扩展,必须等到每一轮的结果都出来(上例是四轮),再相加统一写入内存。

如果改成小端序的乘法,就不需要等待下一轮的结果,每一轮都可以直接写入内存。

上图是小端序的24165乘以3841。小端序的乘法是向右进位,也就是向右边扩展,左边的边界不变。每一轮结果写入内存后,就不需要移动,后面有变化只需要改动对应的位就行了。

因此,小端序的乘法有明显优势。

七、任意精度整数

上一个例子的从低位开始计算的特性,对于任意精度整数特别有用。任意精度整数又称大整数,可以存放任意大小的整数。

它的内部实现是把整数分成一个个较小的单位,通常是 uint32(无符号32位整数)或 uint64(无符号64位整数),按顺序组合在一起。

如果是大端序,第一个 u64 就是这个整数最大的部分。运算时,一旦这个数发生变化,需要进位,后面的所有位都必须移动和改写。小端序发生进位时,往往就不需要所有位移动。

小端序的另一个好处是,如果逐字节的运算从个位数开始(比如乘法和加法),可以从左到右依次运算一个个 u64,算完上一个再读取下一个。大端序就不行,必须读取整个数以后再进行运算。

八、更改类型

最后一个例子是,C 语言有一种 cast 操作,可以强制改变变量的数据类型,比如把32位整数强行改变为16位整数。

上图中,32位整数0x00000001更改为16位整数0x0001,大端序是截去前面两个字节,这时指向这个地址的指针必须向后移动两个字节。

小端序就没有这个问题,截去的是后面两个字节,第一位的地址是不变的,所以指针不需要移动。

九、总结

综上所述,大端序和小端序各自的优势如下。

如果需要逐位运算,或者需要到从个位数开始运算,都是小端序占优势。反之,如果运算只涉及到高位,或者数据的可读性比较重要,则是大端序占优势。

十、参考链接

(完)

留言(41条)

厉害。我一直知道有 Big-Endian 和 Little-Endian的区别,没想到这其中有这么大的差别。

还是那么通俗易懂,令人豁然开朗

厉害,有学到

説好的端午節休息一天,還不忘科普,學習了,謝謝你的分享。

关于判断正负,严格来说正负号的位置一般在第一个比特位(大端序),小端序则是最后一个字节的第一个比特位。奇偶性也是一样,小端序判断奇偶性的位置在第一个字节的最后一个比特位。当然计算机都是一个字节一个字节读的,说最后一个和第一个也算合理。

比较大小的说法我也是第一次听说。只知道汇编里比较大小是做减法看正负,原来还有这种比较法?

你的说法在单片机里环境下可能是对的,但在的大多运算都是 内存-->cache-->寄存器, 并不需要逐个字节比较。

纯粹瞎扯蛋

我认为小端更优雅,因为进位制的位数的自然排列就是从小端开始的。
这里的自然排列是指,每个数位在该排列下的序数不变,不论数值如何。
在小端序下,个位永远是第 0 位,十位永远是第 1 位;而在大端序下这序数就会随数值的总位数而变化。
而判断符号位的困难在小端序下不应该存在:整型的位宽是确定的,程序应当直接硬编码比较对应的位数。
如果是不定位宽的大整数,又为何不将符号这种与数值无关的信息记在数据头里呢?比如这样:

```cpp
struct BigInt {
unsigned char byteCount;
bool sign;
};
```

或者干脆把符号记在位宽字段里就好了!这又归化到前面的硬编码情况了。

对于 RTL 语言来说,书本,网页通常是从右到左的顺序,阮老师啥时候出一期网页的RTL相关的教程。

翻译成低尾端(地址值放低字节,小端),高尾端(低地址放高字节,大端);这样容易记住;

"上图是小端序的24165乘以3841",阮老师,这里是不是写错了。

但内存不是可以随机读写吗

这篇文章可能是不懂计算机原理的人写的。从第二点到第八点都是人类的思维。计算机里面数据在寄存器里面移动,然后在逻辑运算单元里面计算,与字节序没有关系。这些优缺点对于计算机来说都不存在。

刷算法遇到过,只纠结了小端序的进位,还是很有用的。

简明有趣

这文章越来越水了

来个真正通俗易懂的RUST教程吧,阮老师。

这不是计算机专业的基础知识?

挺好的,感谢分享,至于有些小伙伴可能对这块已经有过比较深刻的理解,我只能说——有则改之无则加勉。

阮老师,最近网站时常打不开,是不是有人攻击

感谢!刚好在复习计算机组成原理

阮老师,您什么时候能出一个vue的教程呢,官方的教程写的晦涩难懂,看的很费劲呢

引用Donald的发言:

这篇文章可能是不懂计算机原理的人写的。从第二点到第八点都是人类的思维。计算机里面数据在寄存器里面移动,然后在逻辑运算单元里面计算,与字节序没有关系。这些优缺点对于计算机来说都不存在。

那你知道逻辑运算单元里面如何计算吗? 谁给寄存器和逻辑单元发指令吗?
相信评论时的你不知道或者知道一点点皮毛,也讲不清楚明白。

引用Jade的发言:

那你知道逻辑运算单元里面如何计算吗? 谁给寄存器和逻辑单元发指令吗?
相信评论时的你不知道或者知道一点点皮毛,也讲不清楚明白。

其实他指出的问题是有道理的,博主可能是为了方便大家理解(用十进制来打比方,方便所有人去理解,但我们实际上知道计算机并不 care 数字的进制是什么,它只是用一组 bits 去表达这个数字,所以可以看做是二进制的。)。但是诸如判断奇数偶数,符号这些实际上对计算机是不存在的,cpu 内部固定采用比如说 little-endian。比如判断一个数字是否为奇数或者偶数,只需要判断 x & 1 的结果是不是 0 就可以知道了,是并不存在需要从左或者从右去看这个数字的。
big-endian 通常又称为“网络序”,因为很多数字在拼成数据包的时候,使用的是 big-endian。字节序是一个数字的 bits 在存储层面方面的概念,存在字节序的情况,通常是这个数字或者这个字符的 code point 由多个字节组成,那么在表达这个数字的时候,把这个数字看做一堆 bits,bits 按照它所在的位有一个重要性,越靠“左”(权重越大)的 bit 我们叫做它越重要,那么如果我们把最重要的那些 bits 放在地址值小的字节里,这就叫 big-endian。反之,就叫 little-endian。

一个字节由 8 个 bits 组成,这 8 个 bits 就是纯数学或者逻辑上的概念,是不存在像字节那样的顺序的。表达一个数字的多个字节,之所以有一个顺序,是因为一个字节是最小的可寻址单位,或者说寻址的分辨率和粒度就是字节。一个字节就相当于一个原子。

豁然开朗

@fafa1234:

感谢解答,你说的有些专业名字我不太清楚,从学习探索技术角度来说我觉得文章上表达的东西是ok的,你说的x&1的位运算我理解,但是这也是可以深究的:
一个数字如果是16个字节表达如:0xfff7,对这个数字判断奇偶性

A按你说的x&1的计算方式二进制呈现: 1111 1111 1111 0111 & 1
B按照本博客文中的表达,是可以更简单的表达就是 0111 & 1,甚至 1 & 1

这可能还不明显感觉有什么区别,有一定硬件(芯片)知识的学者知道位运算实现其实就是三极管的运算,这需要耗电和耗费三极管资源,达到同一目的却有使用16个三极管与1个三极管的区别。
这虽然微小,即使目前计算机芯片没做到这个地步,但这是可以进步的,不应该停留在固定的框架去说逻辑思维。

通俗易懂

非常棒,学习到了

「比较大小」那一段的配图我觉得放反了,竖线代表低位地址,可作为对齐的一个标准。

引用Jade的发言:

那你知道逻辑运算单元里面如何计算吗? 谁给寄存器和逻辑单元发指令吗?
相信评论时的你不知道或者知道一点点皮毛,也讲不清楚明白。

我当然知道。而且非常知道。
这篇文章客气来说是不清楚计算机原理。其实就是在乱弹琴。希望作者把这篇文章撤下,以免误认子弟。

大小端从历史上来说只是不同的公司的竞争罢了.. 对于计算机一次取一个 word 或者是 double word,大小端并没有区别

一峰老师能出一篇讲解utf-8为什么没有大小端问题的博文么?

在下资质愚顿,现有网络资料没有理解这个问题。

引用27rabbit的发言:

大小端从历史上来说只是不同的公司的竞争罢了.. 对于计算机一次取一个 word 或者是 double word,大小端并没有区别

你接触计算机时,计算机字长是32位,不代表计算机诞生起就是32位。玩过红白机吗?那是8位CPU。

针对 modbus 等传输控制协议来说,还是很有用的。

引用Jade的发言:

那你知道逻辑运算单元里面如何计算吗? 谁给寄存器和逻辑单元发指令吗?
相信评论时的你不知道或者知道一点点皮毛,也讲不清楚明白。

其实人家说的没错,最简单的一点来说,所谓比较大小读到第一位就读不到数了,你是听谁说的?计算机知道后面的(62位没有数了吗,2的二进制01,64位的整数后面还有62位)如果CPU不往后读,他是怎么知道后面没有数了的

@Jade:

从学习探索技术的角度上来讲,这就更加不可取了。以己昏昏能使人昭昭?

引用Donald的发言:

我当然知道。而且非常知道。
这篇文章客气来说是不清楚计算机原理。其实就是在乱弹琴。希望作者把这篇文章撤下,以免误认子弟。

没错。这种人明显就是看了一点计算机知识,或者只是了解一点高级语言的特性,就拿来想当然的解释底层的原理。以己昏昏欲使人昭昭;-)

总记不住,时长来看看这个通俗易懂的讲解。ps 楼上有些键盘侠好像别人做啥都能找到角度攻击,也是厉害

冷知识:ARM可能是小端,也可能是大端。

https://developer.arm.com/documentation/102376/0200/Alignment-and-endianness/Endianness

“For data accesses, it is IMPLEMENTATION DEFINED whether both little-endian and big-endian are supported. And if only one is supported, it is IMPLEMENTATION DEFINED which one is supported.”

引用轶名的发言:

一峰老师能出一篇讲解utf-8为什么没有大小端问题的博文么?

在下资质愚顿,现有网络资料没有理解这个问题。

因为“位置”信息是自包含的。好像是由开头几个1决定的,具体记不清了。

我要发表看法

«-必填

«-必填,不公开

«-我信任你,不会填写广告链接