字节序
在各种计算机体系结构中,对于字节、字等的存储机制有所不同,因而引发了计算机通信领域中一个很重要的问题,即通信双方交流的信息单元(比特、字节、字、双字等等)应该以什么样的顺序进行传送。
如果不达成一致的规则,通信双方将无法进行正确的编/译码从而导致通信失败
目前在各种体系的计算机中通常采用的字节存储机制主要有两种:Big-Endian
和 Little-Endian
什么是字节序
字节序,顾名思义字节的顺序,就是大于一个字节类型的数据在内存中的存放顺序(一个字节的数据当然就无需谈顺序的问题了)
字节序分为两类:Big-Endian
和 Little-Endian
,引用标准的 Big-Endian
和 Little-Endian
的定义如下:
- Little-Endian(小端序):就是低位字节排放在内存的低地址端,高位字节排放在内存的高地址端。
- Big-Endian(大端序):就是高位字节排放在内存的低地址端,低位字节排放在内存的高地址端。
通俗来说就是高位字节在前,低位字节在后,这是人类读写数值的方法。 - 网络字节序:
TCP/IP
各层协议将字节序定义为Big-Endian
(这与主机序相反),因此TCP/IP
协议中使用的字节序通常称之为网络字节序
为什么会有小端字节序
计算机电路先处理低位字节,效率比较高,因为计算都是从低位开始的。所以,计算机的内部处理都是小端字节序。
但是,人类还是习惯读写大端字节序。所以,除了计算机的内部处理,其他的场合几乎都是大端字节序,比如网络传输和文件储存
计算机处理字节序的时候,不知道什么是高位字节,什么是低位字节。它只知道按顺序读取字节,先读第一个字节,再读第二个字节,
如果是大端字节序,先读到的就是高位字节,后读到的就是低位字节。小端字节序正好相反
什么是高/低地址端
首先我们要知道我们 C 程序映像中内存的空间布局情况:在《C专家编程》中或者《Unix环境高级编程》中有关于内存空间布局情况的说明,大致如下:
----------------------- 最高内存地址 0xffffffff
栈底
栈
栈顶
-----------------------
NULL (空洞)
-----------------------
堆
-----------------------
未初始化的数据
----------------------- 统称数据段
初始化的数据
-----------------------
正文段(代码段)
----------------------- 最低内存地址 0x00000000
例如果我们在栈上分配一个unsigned char buf[4]
,那么这个数组变量在栈上是如何布局的呢?
栈底 (高地址)
----------
buf[3]
buf[2]
buf[1]
buf[0]
----------
栈顶(低地址)
什么是高/低字节
如果我们有一个32位无符号整型 0x12345678
,那么高位是什么,低位又是什么呢?其实很简单。在十进制中我们都说靠左边的是高位,靠右边的是低位,在其他进制也是如此。
就拿 0x12345678
来说,从高位到低位的字节依次是 0x12
、0x34
、0x56
和 0x78
。
以unsigned int value = 0x12345678
为例,分别看看在两种字节序下其存储情况,我们可以用 unsignedchar buf[4]
来表示 value:
Big-Endian: 低地址存放高位
栈底 (高地址) --------------- buf[3] (0x78) -- 低位 buf[2] (0x56) buf[1] (0x34) buf[0] (0x12) -- 高位 --------------- 栈顶 (低地址)
Little-Endian: 低地址存放低位
栈底 (高地址) --------------- buf[3] (0x12) -- 高位 buf[2] (0x34) buf[1] (0x56) buf[0] (0x78) -- 低位 -------------- 栈 顶 (低地址)
Big-Endian
计算机体系结构中一种描述多字节存储顺序的术语,在这种机制中最重要字节(MSB)存放在最低端的地址上。采用这种机制的处理器有
IBM3700
系列、PDP-10
、Mortolora
微处理器系列和绝大多数的RISC
处理器。
双字节数 0x1234
以 Big-Endian
的方式存在起始地址 0x00000020
中
+----------+
| 0x34 |<-- 0x00000021
+----------+
| 0x12 |<-- 0x00000020
+----------+
在Big-Endian中,对于bit序列中的序号编排方式如下(以双字节数 0x8B8A 为例):
bit 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
+-----------------------------------------+
val | 1 0 0 0 1 0 1 1 | 1 0 0 0 1 0 1 0 |
+----------------------------------------+
Little-Endian
计算机体系结构中一种描述多字节存储顺序的术语,在这种机制中最不重要字节(LSB)存放在最低端的地址上。
采用这种机制的处理器有PDP-11
、VAX
、Intel
系列微处理器和一些网络通信设备
。该术语除了描述多字节存储顺序外还常常用来描述一个字节中各个比特的排放次序
双字节数0x1234
以 Little-Endian
的方式存在起始地址 0x00000020
中:
+----------+
| 0x12 |<-- 0x00000021
+----------+
| 0x34 |<-- 0x00000020
+----------+
在 Little-Endian
中,对于bit序列中的序号编排和Big-Endian
刚好相反,其方式如下(以双字节数0x8B8A
为例):
bit 15 14 13 12 11 10 9 8 7 65 4 3 2 1 0
+-----------------------------------------+
val | 1 0 0 0 1 0 1 1 | 1 0 0 0 1 0 1 0 |
+-----------------------------------------+
通常我们说的主机序(Host Order)就是遵循 Little-Endian
规则。所以当两台主机之间要通过TCP/IP
协议进行通信的时候就需要调用相应的函数进行主机序(Little-Endian
)和网络序(Big-Endian
)的转换。
Middle-Endian
除了Big-Endian
和Little-Endian
之外的多字节存储顺序就是Middle- Endian
,比如以4个字节为例:像以3-4-1-2
或者2-1-4-3
这样的顺序存储的就是Middle-Endian
。
这种存储顺序偶尔会在一些小型机体系中的十进制数的压缩格式中出现。
优缺点
- 检查奇偶性
小端序优势最明显的,大概就是检查奇偶性,即通过查看个位数,确定某个数字是奇数还是偶数
以123456
为例,大端序从左到右排列,计算机必须一直读到最后一位的个位数6,才能确定这是偶数。
小端序是从右到左排列,个位数在第一位。所以,只要读取第一位,就能确定它是偶数
检查正负号
大端序的符号位在左边第一位,小端序的符号位在右边最后一位。所以,大端序有优势,只看第一位就能知道是不是负数
比较大小
小端序有优势
例如比较 62576,594,2
23462576
594
2
是大端序排列,因为是从左到右排列,所以三个数字在右边个位数对齐。比较大小时,计算机就不得不读取每一个数的所有位,直到个位数,再进行比较
如果改成小端序,就是下面的排列方式
76526432
495
2
小端序是从右到左,所以三个数字在第一位对齐。计算机就不需要读取所有位,哪个数字先读不到下一位,就是最小的。比如,2这个数字就没有第二位,所以读到第二位时,就知道它是最小的
- 乘法
乘法是逐位相乘,每一轮乘法都要向前进位
4165
x 841
--------
4165
16660
33320
--------
3502765
大端序的4165乘以841。大端序的乘法是向左进位,也就是向左边扩展,必须等到每一轮的结果都出来(上例是3轮),再相加统一写入内存
如果改成小端序的乘法,就不需要等待下一轮的结果,每一轮都可以直接写入内存
5614
148 x
--------
5614
06661
02333
--------
5672053
小端序的乘法是向右进位,也就是向右边扩展,左边的边界不变。每一轮结果写入内存后,就不需要移动,后面有变化只需要改动对应的位就行了
- 任意精度整数
任意精度整数又称大整数,可以存放任意大小的整数
它的内部实现是把整数分成一个个较小的单位,通常是 uint32
(无符号32位整数)或 uint64
(无符号64位整数),按顺序组合在一起
如果是大端序,第一个 u64 就是这个整数最大的部分。运算时,一旦这个数发生变化,需要进位,后面的所有位都必须移动和改写。小端序发生进位时,往往就不需要所有位移动
小端序的另一个好处是,如果逐字节的运算从个位数开始(比如乘法和加法),可以从左到右依次运算一个个 u64,算完上一个再读取下一个。大端序就不行,必须读取整个数以后再进行运算
- 更改类型
比如把32位整数强行改变为16位整数
32位整数0x00000001
更改为16位整数0x0001
,大端序是截去前面两个字节,这时指向这个地址的指针必须向后移动两个字节
小端序就没有这个问题,截去的是后面两个字节,第一位的地址是不变的,所以指针不需要移动
参考链接
https://www.ruanyifeng.com/blog/2022/06/endianness-analysis.html
https://www.cnblogs.com/onepixel/p/7468343.html