CPU字节顺序Big Endian 和 Little Endian 详解
一、Endian 的起源
在各种计算机体系结构中,对于字节、字等的存储机制有所不同,因而引发了计算机通信领域中一个很重要的问题,即通信双方交流的信息单元(比特、字节、字、双字等等)应该以什么样的顺序进行传送。如果不达成一致的规则,通信双方将无法进行正确的编/译码从而导致通信失败。
1980 年,Danny Cohen 在其著名的论文”On Holy Wars and a Plea for Peace”中为了平息一场关于在消息中字节该以什么样的顺序进行传送的争论而引用了该词。该文中,Cohen 非常形象贴切地把支持从一个消息序列的最高位开始传送的那伙人叫做 Big-Endians,支持从最低位开始传送的相对应地叫做 Little-Endians。此后 Endian 这个词便随着这篇论文而被广为采用。
二、字节序之 Little-Endian&Big-Endian
首先,明确一点,咱们接触到的物理单元最小都是字节;因此,无论是 big endian,还是 little endian,都是针对多个字节的序列而言的;当然,在通信领域中,这里往往是 bit,不过原理也是类似的,稍后我会介绍。
对于字节序列的存储格式,目前有两大阵营,那就是 Motorola 的 PowerPC 系列 CPU 和 Intel 的 x86 系列 CPU。PowerPC 系列采用 big endian 方式存储数据,而 x86 系列则采用 little endian 方式存储数据。那么究竟什么是 big endian,什么又是 little endian 呢?
1)Little-endian:将低序字节存储在起始地址(低位编址)
2)Big-endian:将高序字节存储在起始地址(高位编址)
举个例子:
如果我们将 0x1234abcd 写入到以 0x0000 开始的内存中,则结果为;
address | big-endian | little-endian |
---|---|---|
0x0000 | 0x12 | 0xcd |
0x0001 | 0x34 | 0xab |
0x0002 | 0xab | 0x34 |
0x0003 | 0xcd | 0x12 |
注:每个地址存 1 个字节,2 位 16 进制数是 1 个字节(0xFF=11111111);
为什么要注意字节序的问题呢?你可能这么问。当然,如果你写的程序只在单机环境下面运行,并且不和别人的程序打交道,那么你完全可以忽略字节序的存在。
但是,如果你的程序要跟别人的程序产生交互呢?在这里我想说说两种语言。C/C++ 语言编写的程序里数据存储顺序是跟编译平台所在的 CPU 相关的,而 Java 编写的程序则唯一采用 big endian 方式来存储数据。
试想,如果你用 C/C++ 语言在 x86 平台下编写的程序跟别人的 Java 程序互通时会产生什么结果?就拿上面的 0x12345678 来说,你的程序传递给别人的一个数据,将指向 0x12345678 的指针传给了 Java 程序,由于 Java 采取 big endian 方式存储数据,很自然的它会将你的数据翻译为 0x78563412。什么?竟然变成另外一个数字了?是的,就是这种后果。因此,在你的 C 程序传给 Java 程序之前有必要进行字节序的转换工作。
无独有偶,所有网络协议也都是采用 big endian 的方式来传输数据的。所以有时我们也会把 big endian 方式称之为网络字节序。当两台采用不同字节序的主机通信时,在发送数据之前都必须经过字节序的转换成为网络字节序后再进行传输。
目前应该 little endian 是主流,因为在数据类型转换的时候(尤其是指针转换)不用考虑地址问题。
三、比特序之 Little-Endian&Big-Endian
可是有朋友仍然会问,CPU 存储一个字节的数据时其字节内的 8 个比特之间的顺序是否也有 big endian 和 little endian 之分?或者说是否有比特序的不同?
实际上,这个比特序是同样存在的。下面以数字 0xB4(10110100)用图加以说明。
MSB 的意思是:全称为 Most Significant Bit,在二进制数中属于最高有效位,MSB 是最高加权位,与十进制数字中最左边的一位类似。
LSB 的意思是:全称为 Least Significant Bit,在二进制数中意为最低有效位,一般来说,MSB 位于二进制数的最左侧,LSB 位于二进制数的最右侧。
Big Endian
msb------------------------>lsb
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 1 | 0 | 1 | 1 | 0 | 1 | 0 | 0 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Little Endian
lsb-------------------------->msb
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 0 | 0 | 1 | 0 | 1 | 1 | 0 | 1 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
实际上,由于 CPU 存储数据操作的最小单位是一个字节,其内部的比特序是什么样对我们的程序来说是一个黑盒子。也就是说,你给我一个指向 0xB4 这个数的指针,对于 big endian 方式的 CPU 来说,它是从左往右依次读取这个数的 8 个比特;而对于 little endian 方式的 CPU 来说,则正好相反,是从右往左依次读取这个数的 8 个比特。而我们的程序通过这个指针访问后得到的数就是 0xB4,字节内部的比特序对于程序来说是不可见的,其实这点对于单机上的字节序来说也是一样的。
那可能有人又会问,如果是网络传输呢?会不会出问题?是不是也要通过什么函数转换一下比特序?嗯,这个问题提得很好。假设 little endian 方式的 CPU 要传给 big endian 方式 CPU 一个字节的话,其本身在传输之前会在本地就读出这个 8 比特的数,然后再按照网络字节序的顺序来传输这 8 个比特,这样的话到了接收端不会出现任何问题。而假如要传输一个 32 比特的数的话,由于这个数在 littel endian 方存储时占了 4 个字节,而网络传输是以字节为单位进行的,little endian 方的 CPU 读出第一个字节后发送,实际上这个字节是原数的 LSB,到了接收方反倒成了 MSB 从而发生混乱。
testcpu_endian.c
#include <stdio.h>
union {
unsigned long bits32;
unsigned char bytes[4];
} value;
int isLittleEndian() {
value.bytes[0] = 0;
value.bytes[1] = 1;
value.bytes[2] = 0;
value.bytes[3] = 0;
return value.bits32 == 256;
}
int main() {
if( isLittleEndian())
printf("is little endian! \n");
else
printf("is big endian! \n");
return 0;
}