参看:详解大端模式和小端模式
一、什么是大端和小端
所谓的大端模式,就是高位字节排放在内存的低地址端,低位字节排放在内存的高地址端。
所谓的小端模式,就是低位字节排放在内存的低地址端,高位字节排放在内存的高地址端。
简单来说:大端——高尾端,小端——低尾端
举个例子,比如数字 0x12 34 56 78在内存中的表示形式为:
1)大端模式:
低地址 -----------------> 高地址
0x12 | 0x34 | 0x56 | 0x78
2)小端模式:
低地址 ------------------> 高地址
0x78 | 0x56 | 0x34 | 0x12
可见,大端模式和字符串的存储模式类似。
3)下面是两个具体例子:
内存地址 | 小端模式存放内容 | 大端模式存放内容 |
0x4000 | 0x34 | 0x12 |
0x4001 | 0x12 | 0x34 |
32bit宽的数0x12345678在Little-endian模式以及Big-endian模式)CPU内存中的存放方式(假设从地址0x4000开始存放)为:
内存地址 | 小端模式存放内容 | 大端模式存放内容 |
0x4000 | 0x78 | 0x12 |
0x4001 | 0x56 | 0x34 |
0x4002 | 0x34 | 0x56 |
0x4003 | 0x12 | 0x78 |
4)大端小端没有谁优谁劣,各自优势便是对方劣势:
小端模式 :强制转换数据不需要调整字节内容,1、2、4字节的存储方式一样。
大端模式 :符号位的判定固定为第一个字节,容易判断正负。
二、数组在大端小端情况下的存储:
以unsigned int value = 0x12345678为例,分别看看在两种字节序下其存储情况,我们可以用unsigned char 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) -- 低位
--------------
低地址
三、为什么会有大小端模式之分呢?
这是因为在计算机中,我们是以字节为单位的,每个地址单元都对应着一个字节,一个字节为 8 bit。但是在C 语言中除了 8 bit 的char之外,还有 16 bit 的 short型,32bit的long型(要看具体的编译器),另外,对于位数大于8位的处理器,例如16位或者32位的处理器,由于寄存器宽度大于一个字节,那么必然存在着一个如果将多个字节安排的问题。因此就导致了大端存储模式和小端存储模式。例如一个16bit的short型 x ,在内存中的地址为 0x0010,x 的值为0x1122,那么0x11位高字节,0x22位低字节。对于大端模式,就将0x11放在低地址中,即0x0010中,0x22放在高地址中,即0x0011中。小端模式,刚好相反。我们常用的X86结构是小端模式,而KEIL C51则为大端模式。很多的ARM,DSP都为小端模式。有些ARM处理器还可以由硬件来选择是大端模式还是小端模式。
四、如何判断机器的字节序 (重点)
1. 共用体声明和共用体变量定义
共用体(参考“共用体”百科词条)是一种特殊形式的变量,使用关键字union来定义
共用体(有些人也叫"联合")声明和共用体变量定义与结构体十分相似。其形式为:
union 共用体名{
数据类型 成员名;
数据类型 成员名;
...
} 变量名;
参看:
共用体表示几个变量共用一个内存位置,在不同的时间保存不同的数据类型和不同长度的变量。在union中,所有的共用体成员共用一个空间,并且同一时间只能储存其中一个成员变量的值。
下例表示声明一个共用体foo:
再用已声明的共用体可定义共用体变量。例如,用上面说明的共用体定义一个名为bar的共用体变量, 可写成:
union foo bar;
在共用体变量bar中, 整型变量 i 和字符变量 c 共用同一内存位置。
当一个共用体被声明时, 编译程序自动地产生一个变量, 其长度为联合中最大的变量长度的整数倍。以上例而言,最大长度是double数据类型,所以foo的内存空间就是double型的长度。
在这个union中,foo的内存空间的长度为12,是int型的3倍,而并不是数组的长度10。若把int改为double,则foo的内存空间为16,是double型的两倍。
2. 共用体和结构体的区别
1)共用体和结构体都是由多个不同的数据类型成员组成, 但在任何同一时刻, 共用体只存放了一个被选中的成员, 而结构体的所有成员都存在。
2.)对于共用体的不同成员赋值, 将会对其它成员重写, 原来成员的值就不存在了, 而对于结构体的不同成员赋值是互不影响的。
Little Endian : x86、DEC
ARM既可以工作在大端模式,也可以工作在小端模式。
2)常见文件的字节序
Adobe PS – Big EndianBMP – Little Endian
DXF(AutoCAD) – Variable
GIF – Little Endian
JPEG – Big Endian
MacPaint – Big Endian
RTF – Little Endian
htons() //16位无符号短整型的主机字节顺序到网络字节顺序的转换 (小端->>大端)
ntohl() //32位无符号整型的网络字节顺序到主机字节顺序的转换 (大端->>小端)
ntohs() //16位无符号短整型的网络字节顺序到主机字节顺序的转换 (大端->>小端)
在对普通文件进行处理也需要考虑端模式问题。在大端模式的处理器下对文件的32,16位读写操作所得到的结果与小端模式的处理器不同。单纯从软件的角度理解上远远不能真正理解大小端模式的区别。事实上,真正的理解大小端模式的区别,必须要从系统的角度,从指令集,寄存器和数据总线上深入理解,大小端模式的区别。
以下内容了解:
1、从系统的角度理解端模式
MSB:MoST Significant Bit ------- 最高有效位
LSB:Least Significant Bit ------- 最低有效位
PowerPC处理器主导网络市场,可以说绝大多数的通信设备都使用PowerPC处理器进行协议处理和其他控制信息的处理,这也可能也是在网络上的绝大多数协议都采用大端编址方式的原因。因此在有关网络协议的软件设计中,使用小端方式的处理器需要在软件中处理端模式的转变。而Pentium主导个人机市场,因此多数用于个人机的外设都采用小端模式,包括一些在网络设备中使用的PCI总线,Flash等设备,这也要求在硬件设计中注意端模式的转换。
本文提到的小端外设是指这种外设中的寄存器以小端方式进行存储,如PCI设备的配置空间,NOR FLASH中的寄存器等等。对于有些设备,如DDR颗粒,没有以小端方式存储的寄存器,因此从逻辑上讲并不需要对端模式进行转换。在设计中,只需要将双方数据总线进行一一对应的互连,而不需要进行数据总线的转换。
如果从实际应用的角度说,采用小端模式的处理器需要在软件中处理端模式的转换,因为采用小端模式的处理器在与小端外设互连时,不需要任何转换。而采用大端模式的处理器需要在硬件设计时处理端模式的转换。大端模式处理器需要在寄存器,指令集,数据总线及数据总线与小端外设的连接等等多个方面进行处理,以解决与小端外设连接时的端模式转换问题。在寄存器和数据总线的位序定义上,基于大小端模式的处理器有所不同。
一个采用大端模式的32位处理器,如基于E500内核的MPC8541,将其寄存器的最高位msb(most significant bit)定义为0,最低位lsb(lease significant bit)定义为31;而小端模式的32位处理器,将其寄存器的最高位定义为31,低位地址定义为0。与此向对应,采用大端模式的32位处理器数据总线的最高位为0,最高位为31;采用小端模式的32位处理器的数据总线的最高位为31,最低位为0。
大小端模式处理器外部总线的位序也遵循着同样的规律,根据所采用的数据总线是32位,16位和8位,大小端处理器外部总线的位序有所不同。大端模式下32位数据总线的msb是第0位,MSB是数据总线的第0~7的字段;而lsb是第31位,LSB是第24~31字段。小端模式下32位总线的msb是第31位,MSB是数据总线的第31~24位,lsb是第0位,LSB是7~0字段。大端模式下16位数据总线的msb是第0位,MSB是数据总线的第0~7的字段;而lsb是第15位,LSB是第8~15字段。小端模式下16位总线的msb是第15位,MSB是数据总线的第15~7位,lsb是第0位,LSB是7~0字段。大端模式下8位数据总线的msb是第0位,MSB是数据总线的第0~7的字段;而lsb是第7位,LSB是第0~7字段。小端模式下8位总线的msb是第7位,MSB是数据总线的第7~0位,lsb是第0位,LSB是7~0字段。
由上分析,我们可以得知对于8位,16位和32位宽度的数据总线,采用大端模式时数据总线的msb和MSB的位置都不会发生变化,而采用小端模式时数据总线的lsb和LSB位置也不会发生变化。
为此,大端模式的处理器对8位,16位和32位的内存访问(包括外设的访问)一般都包含第0~7字段,即MSB。小端模式的处理器对8位,16位和32位的内存访问都包含第7~0位,小端方式的第7~0字段,即LSB。由于大小端处理器的数据总线其8位,16位和32位宽度的数据总线的定义不同,因此需要分别进行讨论在系统级别上如何处理端模式转换。在一个大端处理器系统中,需要处理大端处理器对小端外设的访问。
虽然很多时候,字节序的工作已由编译器完成了,但是在一些小的细节上,仍然需要去仔细揣摩考虑,尤其是在以太网通讯、MODBUS通讯、软件移植性方面。这里,举一个MODBUS通讯的例子。在MODBUS中,数据需要组织成数据报文,该报文中的数据都是大端模式,即低地址存高位,高地址存低位。假设有一16位缓冲区m_RegMW[256],因为是在x86平台上,所以内存中的数据为小端模式:m_RegMW[0].low、m_RegMW[0].high、m_RegMW[1].low、m_RegMW[1].high……
为了方便讨论,假设m_RegMW[0] = 0x3456; 在内存中为0x56、0x34。
现要将该数据发出,如果不进行数据转换直接发送,此时发送的数据为0x56,0x34。而Modbus是大端的,会将该数据解释为0x5634而非原数据0x3456,此时就会发生灾难性的错误。所以,在此之前,需要将小端数据转换成大端的,即进行高字节和低字节的交换,此时可以调用步骤五中的函数BigtoLittle16(m_RegMW[0]),之后再进行发送才可以得到正确的数据