数据在内存中的存储方式( Big Endian和Little Endian的区别 )(x86系列则采用little endian方式存储数据)
1.故事的起源
“endian”这个词出自《格列佛游记》。小人国的内战就源于吃鸡蛋时是究竟从大头(Big-Endian)敲开还是从小头(Little-Endian)敲开,由此曾发生过六次叛乱,其中一个皇帝送了命,另一个丢了王位。
我们一般将endian翻译成“字节序”,将big endian和little endian称作“大尾”和“小尾”。
2.什么是Big Endian和Little Endian?
在设计计算机系统的时候,有两种处理内存中数据的方法。一种叫为little-endian,存放在内存中最低位的数值是来自数据的最右边部分(也就是数据的最低位部分)。 比如某些文件需要在不同平台处理,或者通过Socket通信。这方面我们可以借助ntohl(), ntohs(), htonl(), and htons()函数进行格式转换,
个人补充:一个操作数作htonl或ntohl结果不一定相同,当机器字节序跟网络字节序刚好是仅仅big endian和little endian的区别时是相同的。
3. 如何理解Big Endian和Little Endian
举个例子:
int a = 1;
a这个数本身的16进制表示是0x00 00 00 01
在内存中怎么存储呢?
如果你的CPU是intel x86架构的(基本上就是通常我们说的奔腾cpu),那么就是0x01 0x00 0x00 0x00 , 这也就是所谓的little-endian, 低字节存放在内存的低位.
如果你的CPU是老式AMD系列的(很老很老的那种,因为最新的AMD系列已经是x86架构了), 它的字节序就是big-endian, 其内存存储就是 0x00 0x00 0x00 0x01在内存中从高字节开始存放。
现在世界上绝大多数的CPU都是little-endian。
4. 了解big-endian和little-endian有什么作用?
一个重要的作用就是了解在网络上不同的机器间的数据如何传输。 假设我们在网络上有两台机器A和B, 其中A为little-endian,B为big-endian 机器A要传输上面的整数a给机器B,如何传输呢? 过程是这样的:
机器A先把a在内存中的四个字节0x 01 0x00 0x00 0x00转化为网络字节序0x00 0x00 0x00 0x01,然后一个字节一个字节(从0x00到0x01)喂到网络上去 ,然后机器B从网络上一个字节一个字节地取出四个字节0x00 0x00 0x00 0x01后又会转化为本地字节序 0x00 0x00 0x00 0x01后放入内存。因而B正确地得到了来自A的数据a 。如果数据缺少在网络上的字节序转换的话,情况会怎样呢?
机器A先把a由在内存的四个字节0x 01 0x00 0x00 0x00 一个字节一个字节地喂到网络上,然后机器B从网络上一个字节一个字节地收到0x 01 0x00 0x00 0x00并放入到内存中, B认为他收到了0x01000000, 也就是十进制数1677216,这显然是错误的.
5.如何判断系统是Big Endian还是Little Endian?
在/usr/include/中(包括子目录)查找字符串BYTE_ORDER(或_BYTE_ORDER, __BYTE_ORDER),确定其值。这个值一般在endian.h或machine/endian.h文件中可以找到,有时在feature.h中,不同的操作系统可能有所不同。一般来说,Little Endian系统BYTE_ORDER(或_BYTE_ORDER,__BYTE_ORDER)为1234,Big Endian系统为4321。大部分用户的操作系统(如windows, FreeBsd,Linux)是Little Endian的。少部分,如MAC OS ,是Big Endian 的。本质上说,Little Endian还是Big Endian与操作系统和芯片类型都有关系。
在ARM体系中,每个字单元包含4个字节单元或者两个半字单元。在字单元中,4个字节哪一个是高位字节,哪一个是低位字节则有两种不同的格式:big-endian和little-endian格式。在小端模式中,低位字节放在低地址,高位字节放在高地址;在大端模式中,低位字节放在高地址,高位字节放在低地址。
如果将一个32位的整数0x12345678(如用UltraEdit打开某个文件看到的第一行头四个字节是:"00000000h:12 34 56 78")存放到一个整型变量(int)中,这个整型变量(文件内容)采用大端或者小端模式在内存中的存储由下表所示。
地址偏移 |
大端模式 |
小端模式 |
0x00 |
12 |
78 |
0x01 |
34 |
56 |
0x02 |
56 |
34 |
0x03 |
78 |
12 |
对于文件内容 0x12345678,把前面("12")的看为高端字节,后面("78")的看为低端字节,那么可以使用"高高低低"(Little Endian),"高低高低"(Big Endian)的口诀。直观的区分,如果发现内存的内容和文件的内容在顺序上以4个字节颠倒,那么他就是Little Edian。实现Big Endian和Little Endian主要是由编译器指定的,通常是在CCFLAG 加参数,如: -DENDIAN_LITTLE,设定编译为小端字节。实际中用Trace 32可以用Memory Dump查看内存内容,和写入文件比较后判断为大端还是小端。
如果将一个16位的整数0x1234存放到一个短整型变量(short)中。这个短整型变量在内存中的存储在大小端模式由下表所示。
地址偏移 |
大端模式 |
小端模式 |
0x00 |
12 |
34 |
0x01 |
34 |
12 |
由上表所知,采用大小模式对数据进行存放的主要区别在于在存放的字节顺序,大端方式将高位存放在低地址,小端方式将低位存放在低地址。
那么该如何判断CPU是大端模式还是小端模式呢??
在C语言中,联合体union的存放顺序是所有成员都从低地址开始存放的。利用这一特点,可以用联合体变量判断ARM或x86环境下,存储系统是是大端还是小端模式。
具体的代码如下:
#include "stdio.h"
int main()
{
union w
{
int a; //4 bytes
char b; //1 byte
} c;
c.a=1;
if (c.b==1)
printf("It is Little_endian!/n");
else
printf("It is Big_endian!/n");
return 1;
}
数据在存放到内存里的时候,有两种存放方式,即:Big Endian 和 Little Endian,这两个存取方式决定了内存存放数据的原则是 高高低低 原则 还是 高低低高 原则。
高高低低--内存中的高位存放数据的高位,内存中的低位存放数据的低位(Little Endian )
高低低高--内存中的高位存放数据的低位,内存中的低位存放数据的高位(Big Endian )
比如:我有一个数据,是0xA5A1,它在存放到内存中是怎样存放的呢?因为在我们平时的书写中,A5是高位,A1在低位,存放到内存中的时候,A1存放在0x4000这个位置,而A5存放在0x4001这个位置,高位存放在内存的高地址中,低位存放在低地址中,这种方式就是Little Endian 。
下面再粘一篇网友的总结的文章来详细阐述一下这两种方式的区别:
Big Endian 和 Little Endian 模式的区别(转载)
谈到字节序的问题,必然牵涉到两大CPU派系。那就是Motorola的PowerPC系列CPU和Intel的x86系列CPU。PowerPC系列采用big endian方式存储数据,而x86系列则采用little endian方式存储数据。那么究竟什么是big endian,什么又是little endian呢?
其实big endian是指低地址存放最高有效字节(MSB),而little endian则是低地址存放最低有效字节(LSB)。
用文字说明可能比较抽象,下面用图像加以说明。比如数字0x12345678在两种不同字节序CPU中的存储顺序如下所示:
Big Endian
低地址 高地址
----------------------------------------------------------------------------->
| 12 | 34 | 56 | 78 |
Little Endian
低地址 高地址
----------------------------------------------------------------------------->
| 78 | 56 | 34 | 12 |
从上面两图可以看出,采用big endian方式存储数据是符合我们人类的思维习惯的。
为什么要注意字节序的问题呢?你可能这么问。当然,如果你写的程序只在单机环境下面运行,并且不和别人的程序打交道,那么你完全可以忽略字节序的存在。但是,如果你的程序要跟别人的程序产生交互呢?尤其是当你把你在微机上运算的结果运用到计算机群上去的话。
在这里我想说说两种语言。C/C++语言编写的程序里数据存储顺序是跟编译平台所在的CPU相关的,而JAVA编写的程序则唯一采用big endian方式来存储数据。试想,如果你用C/C++语言在x86平台下编写的程序跟别人的JAVA程序互通时会产生什么结果?就拿上面的0x12345678来说,你的程序传递给别人的一个数据,将指向0x12345678的指针传给了JAVA程序,由于JAVA采取big endian方式存储数据,很自然的它会将你的数据翻译为0x78563412。因此,在你的C程序传给JAVA程序之前有必要进行字节序的转换工作。
所有网络协议也都是采用big endian的方式来传输数据的。所以有时我们也会把big endian方式称之为网络字节序。当两台采用不同字节序的主机通信时,在发送数据之前都必须经过字节序的转换成为网络字节序后再进行传输。ANSI C中提供了下面四个转换字节序的宏。
一道C语言的试题:请写一个C函数,若处理器是Big_endian的,则返回0;若是Little_endian的,则返回1。
解答:
int checkCPU()
{
{
union w
{
int a;
char b;
} c;
c.a = 1;
return (c.b == 1);
}
}
嵌入式系统开发者应该对Little-endian和Big-endian模式非常了解。采用Little-endian模式的CPU对操作数的存放方式是从低字节到高字节,而Big-endian模式对操作数的存放方式是从高字节到低字节。例如,16bit宽的数0x1234在Little- endian模式CPU内存中的存放方式(假设从地址0x4000开始存放)为:
内存地址
存放内容
0x4000
0x34
0x400
0x12
而在Big-endian模式CPU内存中的存放方式则为:
内存地址
存放内容
0x4000
0x12
0x4001
0x34
32bit宽的数0x12345678在Little-endian模式CPU内存中的存放方式(假设从地址0x4000开始存放)为:
内存地址
存放内容
0x4000
0x78
0x4001
0x56
0x4002
0x34
0x4003
0x12
而在Big-endian模式CPU内存中的存放方式则为:
内存地址
存放内容
0x4000
0x12
0x4001
0x34
0x4002
0x56
0x4003
0x78
联合体union的存放顺序是所有成员都从低地址开始存放,解答利用该特性,轻松地获得了CPU对内存采用Little-endian还是Big-endian模式读写。