一、Endian的起源
在各种计算机体系结构中,对于字节(byte)、字(bit)的存储机制有所不同,因而,引发了计算机通信领域中一个很重要的问题,即通信双方交流的信息单元(比特、字节、字、双字等等)应该以什么样的顺序进行传输。如果不达成一致的规则,通信双方将无法进行正确的编/译码,从而导致通信失败。
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系列和Intel的x86系列CPU。PowerPC采用Big-Endian方式存储数据,而x86系列则采用Little-Endian方式存储数据。那么究竟什么是Big-Endian,什么又是Little-Endian呢?
- Little-Endian:将低序字节存储在起始地址(低位编址)
- Big-Endian:将高序字节存储在起始地址(高位编址)
举个例子,如果我们将0x1234abcd写入到以0x0000开始的内存中,则结果为:
| address | Big-Endian | Little-Endian |
|---|---|---|
| 0x0000 | 0x12 | 0xcd |
| 0x0001 | 0x34 | 0xab |
| 0x0002 | 0xab | 0x34 |
| 0x0003 | 0xcd | 0x12 |
注:每个地址存一个字节,2位16进制数是一个字节(0xFF = 11111111)。
为什么要注意字节序的问题呢?你可能会这么问。当然,如果你写的程序只在单机环境下面运行,并且不和别人的程序打交道,那么你完全可以忽略字节序的存在。
但是,如果你的程序要和别人的程序产生交互呢?在这里我想说说两种语言。C/C++语言编写的程序里数据存储顺序是跟编译平台所在的CPU相关的,而JAVA编写的程序则唯一采用Big-Endian方式来存储数据。
试想,如果你用C/C++语言在x86平台(x86平台采用Little-Endian存储数据)下编写的程序跟别人的JAVA程序互通时会产生什么结果?就拿上面的0x1234abcd来举例,你的程序传递给别人的一个数据,将指向0x1234abcd的指针传给了JAVA程序,由于JAVA采取Big-Endian方式存储数据,很自然的它会将你的数据翻译为0xcdab3412。什么?竟然变成另外一个数字了?就是这种结果。因此,在你的C程序传给JAVA程序之前有必要进行字节序的转换工作。
无独有偶,所有网络协议也都是采用Big-Endian的方式来传输数据的。所以有时候我们也会把Big-Endian方式称之为网络字节序。当两台采用不同字节序的主机通信时,在发送数据之前都必须经过字节序的转换,成为网络字节序后再进行传输。
目前应该Little-Endian是主流,因为在数据类型转换的时候(尤其是指针转换)不用考虑地址问题。
优缺点对比
Big-Endian:
基于其存储特点,符号位在所表示的数据的内存的第一个字节中,便于快速判断数据的正负和大小(CPU做数值运算时从内存中依顺序依次从低位地址到高位地址取数据进行运算,大端就会最先拿到数据的(高字节的)符号位)。
Little-Endian
基于其存储特点,内存的低地址处存放低字节,所以在强制转换数据时不需要调整字节的内容(比如,把int---4字节强制转换成short---2字节,就可以直接把int数据存储的前两个字节给short就行,因为其前两个字节刚好就是最低的两个字节,符合转换逻辑;另外CPU做数值运算时,从内存中依顺序依次从低位地址到高位地址取数据进行运算,开始只管取值,最后刷新最高位地址的符号位就行,这样的运算方式会更高效一些)。
因为两种模式各有优点,存在“你有我无,你无我有”的特点,所以造就了不同的硬件厂商基于不同的效率(角度)考虑,有了不同的硬件设计支持,最终形成了计算机各个相关领域目前并没有采用统一的字节序,没有统一标准的现状。
目前我们常见的CPU PowerPC、IBM是大端模式,x86是小端模式。ARM既可以工作在大端模式,也可以工作在小端模式,一般ARM都默认是小端模式。一般通讯协议都采用的是大端模式。
另外,常见文件的字节序如下:
| 文件类型 | 字节序 |
|---|---|
| BMP | Little-Endian |
| GIF | Little-Endian |
| JPEG | Big-Endian |
| RTF | Little-Endian |
| Adobe PS | Bit-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比特的数的话,由于这个数在Little-Endian方存储时占了4个字节,而网络传输是以字节为单位进行的,Little-Endian方的CPU读出第一个字节后发送,实际上这个字节是原数的LSB,到了接收方反倒成了MSB,从而发生混乱。
参考资料: