码迷,mamicode.com
首页 > 其他好文 > 详细

(转)大小端模式详解

时间:2015-07-02 17:14:22      阅读:143      评论:0      收藏:0      [点我收藏+]

标签:

int i=1;  

char *p=(char *)&i;  

 if(*p==1)              

 printf("1");     

else           

printf("2");

          大小端存储问题,如果小端方式中(i占至少两个字节的长度)则i所分配的内存最小地址那个字节中就存着1,其他字节是0.大端的话则1在i的最高地址字节处存放,char是一个字节,所以强制将char型量p指向i则p指向的一定是i的最低地址,那么就可以判断p中的值是不是1来确定是不是小端。

 

请写一个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 0x4001
存放内容 0x34 0x12
而在Big-endian模式CPU内存中的存放方式则为:
内存地址 0x4000 0x4001
存放内容 0x12 0x34
32bit宽的数0x12345678在Little-endian模式CPU内存中的存放方式(假设从地址0x4000开始存放)为:
内存地址 0x4000 0x4001 0x4002 0x4003
存放内容 0x78 0x56 0x34 0x12
而在Big-endian模式CPU内存中的存放方式则为:
内存地址 0x4000 0x4001 0x4002 0x4003
存放内容 0x12 0x34 0x56 0x78
联合体union的存放顺序是所有成员都从低地址开始存放,面试者的解答利用该特性,轻松地获得了CPU对内存采用Little-endian还是Big-endian模式读写。如果谁能当场给出这个解答,那简直就是一个天才的程序员。
 
 
补充:
所谓的大端模式,是指数据的低位(就是权值较小的后面那几位)保存在内存的高地址中,而数据的高位,保存在内存的低地址中,这样的存储模式有点儿类似于把数据当作字符串顺序处理:地址由小向大增加,而数据从高位往低位放;   
所谓的小端模式,是指数据的低位保存在内存的低地址中,而数据的高位保存在内存的高地址中,这种存储模式将地址的高低和数据位权有效地结合起来,高地址部分权值高,低地址部分权值低,和我们的逻辑方法一致。   
为什么会有大小端模式之分呢?这是因为在计算机系统中,我们是以字节为单位的,每个地址单元都对应着一个字节,一个字节为 8bit。但是在C语言中除了8bit的char之外,还有16bit的short型,32bit的long型(要看具体的编译器),另外,对于位数大于 8位的处理器,例如16位或者32位的处理器,由于寄存器宽度大于一个字节,那么必然存在着一个如果将多个字节安排的问题。因此就导致了大端存储模式和小 端存储模式。
例如一个16bit的short型x,在内存中的地址为0x0010,x的值为0x1122,那么0x11为高字节,0x22为低字节。
对于大端模式,就将0x11放在低地址中,即0x0010中,0x22放在高地址中,即0x0011中。
小端模式,刚好相反。我们常用的X86结构是小端模 式,而KEIL C51则为大端模式。很多的ARM,DSP都为小端模式。有些ARM处理器还可以由硬件来选择是大端模式还是小端模式。
 
下面这段代码可以用来测试一下你的编译器是大端模式还是小端模式:
 
short int x;
char x0,x1;
x=0x1122;
x0=((char*)&x)[0]; //低地址单元
x1=((char*)&x)[1]; //高地址单元 若x0=0x11,则是大端; 若x0=0x22,则是小端......
上面的程序还可以看出,数据寻址时,用的是低位字节的地址。
 
-----------------------------------------------------------------------------------------------

什么时候要进行大小端字节序的转换?  

short 或者 long的数据在进行通信的时候最好养成:

1、发送的时候使用:htons(l)

2、接受的时候使用:ntohs(l)  而不要理会两边的通信是否需要这么做~~  当然了一般我都不用int型的数据通信,从来都是字符串通信,发送方利用sprintf组织,接收方利用atoi进行转换~~

-----------------------------------------------------------------------------------------------

 

端模式(Endian)的这个词出自Jonathan Swift书写的《格列佛游记》。这本书根据将鸡蛋敲开的方法不同将所有的人分为两类,从圆头开始将鸡蛋敲开的人被归为Big Endian,从尖头开始将鸡蛋敲开的人被归为Littile Endian(这句话最为形象)。小人国的内战就源于吃鸡蛋时是究竟从大头(Big-Endian)敲开还是从小头(Little-Endian)敲开。在计算机业Big Endian和Little Endian也几乎引起一场战争。在计算机业界,Endian表示数据在存储器中的存放顺序。下文举例说明在计算机中大小端模式的区别。

如果将一个32位的整数0x12345678存放到一个整型变量(int)中,这个整型变量采用大端或者小端模式在内存中的存储由下表所示。为简单起见,本文使用OP0表示一个32位数据的最高字节MSB(Most Significant Byte),使用OP3表示一个32位数据最低字节LSB(Least Significant Byte)。

 

地址偏移

大端模式

小端模式

0x00

12(OP0)

78(OP3)

0x01

34(OP1)

56(OP2)

0x02

56(OP2)

34(OP1)

0x03

78(OP3)

12(OP0)

 

小端:较高的有效字节存放在较高的的存储器地址,较低的有效字节存放在较低的存储器地址。 

大端:较高的有效字节存放在较低的存储器地址,较低的有效字节存放在较高的存储器地址。 

如果将一个16位的整数0x1234存放到一个短整型变量(short)中。这个短整型变量在内存中的存储在大小端模式由下表所示。

 

地址偏移

大端模式

小端模式

0x00

12(OP0)

34(OP1)

0x01

34(OP1)

12(OP0)

 

由上表所知,采用大小模式对数据进行存放的主要区别在于在存放的字节顺序,大端方式将高位存放在低地址,小端方式将高位存放在高地址。采用大端方式进行数据存放符合人类的正常思维,而采用小端方式进行数据存放利于计算机处理。到目前为止,采用大端或者小端进行数据存放,其孰优孰劣也没有定论。

有的处理器系统采用了小端方式进行数据存放,如Intel的奔腾。有的处理器系统采用了大端方式进行数据存放,如IBM半导体和Freescale的PowerPC处理器。不仅对于处理器,一些外设的设计中也存在着使用大端或者小端进行数据存放的选择。

因此在一个处理器系统中,有可能存在大端和小端模式同时存在的现象。这一现象为系统的软硬件设计带来了不小的麻烦,这要求系统设计工程师,必须深入理解大端和小端模式的差别。大端与小端模式的差别体现在一个处理器的寄存器,指令集,系统总线等各个层次中。

【用函数判断系统是Big Endian还是Little Endian】

//如果字节序为big-endian,返回true;

//反之为   little-endian,返回false

bool IsBig_Endian()

{     

unsigned short test = 0x1234;    

if(*( (unsigned char*) &test ) == 0x12)       

 returnTRUE;   

else       

 return FALSE;

 

}//IsBig_Endian()

 

附:

大小端的分度值是 byte,即每一个byte都是按照正常顺序,但是byte组装成一个int 或者是 long等时每个byte的摆放位置不同

------------------------------------------------------------------------------------------------

众所周知,同样一组数据,存储和表示的顺序可以是多样的,也就是存储和表示格式是多样的[1]。相同的数据,转换成二进制之后,在不同的计算机存储器中的内部表示也是有区别的,这会对编程产生一定的影响。本文主要探讨大小端存储模式对编程的影响及在编程时应采取的对策。

1 大小端存储模式概述

     计算机中的存储器(内存)由大量的存储元构成。存储元是存储器的最小物理组成单位,用来存放一位二进制数0或1。把这些存储元按相同的位数(通常是1字节8位的1, 2, 4, 8倍)划分成组,组内所有存储元同时读出或写入信息,即为存储单元[2]。每个存储单元都有一个惟一的编号,叫做单元地址。存储单元是CPU访问存储器的基本单位,CPU通过单元地址访问(读或写)相 应的存储单元。不同的计算机,存储单元地址的编排方式有所不同。如果进行编址的最小单位是字,称为按字编址,如图1 (a)所示。如果编址的最小单位是字节,则称按字节编址。因此, CPU一次访问一个存储单元就可能访问若干个独立编址的字节,图1(b)为PDP-11机器,一个存储单元存放2个字节,低字节用偶地址,高字节用奇地址,字地址是2的倍数,即它的低字节的地址。图1(c)为IBM-370机器,一个存储单元存放4个字节,字地址是4的整数倍,即它的高字节的地址(与图1(b)所示情况相反)。图1 存储器的不同编址方式按字节编址是当前存储器编址方式的主流,因为数据处理的最小单位是字节。从软件角度看,存储器就是一个很大的字节数组。通常,CPU和编译器使用不同的格式来编码数据,如不同长度的整数和浮点数,从而支持多种数据类型。程 序中,先定义这些类型的变量,到内存中分配字节空间,当CPU读写这些变量,即访问内存时,往往依据数据类型的不同,一次可读写若干个字节。对于多于一个字节的数据(通常为字节长度的2N倍,N=1, 2, 3),在存储器中有两种存储方式,即定义半字、字、双字与字节之间的对应关系的两种映射机制[3]。一种是数据的低字节部分存放在内存低地址处,高字节部分存放在内存高 地址处,称为小端字节顺序存储法,又称小端存储模式;另一种是高字节数据存放在低地址处,低字节数据存放在高地址处,称为大端字节顺序存储法,又称大端存储模。不难看出,图1(b)所示为小端模式,而图1(c)即为大端模式。支持大端存储模式还是小端存储模式,并不存在技术原因,只是涉及到处理器厂商的立场和习惯。

2 存储模式不同而导致的问题

    对于大多数程序员而言,机器的字节存储顺序是完全不可见的,无论哪一种存储模式的处理器编译出的程序都会得到相同的结果。即对于同一段源代码,单独在小端机器上编译运行,其结果与单独在大端机器上编译运行的结果一样,尽管同一个数据在大小端格式下的内存表示有区别,但在应用程序员和用户眼里,参与算术逻辑运算、写入读出的数据却是无差别的。不过,有些情况下,字节顺序会成为问题。

2. 1 UNIX问题———程序可移植性问题

    最早在将UNIX操作系统的早期版本从PDP-11移植到IBM机器上时,数据“UNIX”在16位字长的小端格式的PDP-11上被表示为2个字4个字节,当被移植到大端存储模式的IBM机器上时,就会变成“NUXI”,这被称为UNIX问题。因此,当在不同存储顺序的处理器间进行程序移植时,需要特别注意存储模式的影响。

2. 2 阅读、解释、共享二进制数据的问题

    对同一段可执行程序进行反汇编,使用反汇编器阅读机器级二进制代码时,在不同存储模式的处理器上会看到不同的结果;在解释、共享以二进制格式存储的数据和使用掩码时,不同的存储顺序会得出不同的结果,例如,某32位小端机器,存储某常量0xE48623A0到某二进制文件,在大端模式下读出的是0xA02386E4,若把该数据看做IPv4地址,则存储并使用合适的掩码进行按位与运算也得考虑到存储模式的影响。

2. 3 网络数据传输的问题

    当不同存储模式的处理器之间通过网络传送二进制数据时,会产生高低字节翻转现象,例如,从32位小端机器,发送某常量0x01234567,发送和接收缓冲区内(地址从低到高)的字节顺序为0x67、0x45、0x23、0x01,接收该数据的对方机器为 32位大端模式,则读出的数值是0x67452301,相比原值,高低字节互换了位置。

3 编程时可采取的对策

    编程时,应考虑该应用是否与存储模式相关,若需要在不同存储模式的处理器之间移植程序、共享数据、网络通信,可以尝试以下对策。若是程序移植,则在程序中添加如下的预编译条件,针对不同存储模式,分别处理,然后在头文件中定义当前处理器和编译器支持的存储模式,如: #define Little-End #ifdefBig_End …… #endif #ifdefLittle_End …… #endif 若是信息共享,则有两种解决方法: (1)以单一存储顺序共享数据,只需解释一种格式,所以解码简单; (2)允许各主机以不同的存储顺序共享数据,但需标记出哪种模式,无需对数据的原顺序进行转化,所以编码容易,当发送方编码和接收方解码采用同一种存储模式时,无需变换字节顺序,可以提高通信效率。

若是网络通信,可以参照并遵循TCP/IP协议定义的标准的网络字节顺序,由发送方处理器先在其内部将发送的数据转换成网络标准,而接收方处理器再将网络标准转换为它的内部表示。Berkeley应用程序接口定义了一套转换函数,如:函数htonl和htons分别将32位长整型和16位短整型数值从主机字节顺序转化成网络字节顺序;而函数ntohl和ntohs则将网络字节顺序转化为主 机字节顺序。

4 案例分析

    ZLG/IP可运行于大端机器,也可运行于小端机器,涉及程序的可移植性; ZLG/IP是嵌入式网络通信协议,必然涉及多个主机间的数据共享和网络传输;由大小端存储模式不同而导致的问题,ZLG/IP都会遇到,它是如何解决的呢?限于篇幅,这里仅摘录IP. c中IP报头的发送、校验、接收函数的部分源代码。存储模式不同不会影响IP报头的字节变量成员的读写,也不会影响IP地址的读写(IP地址按大端顺序采用字节数组存储),所以,只选取IP报头的16位半字变量成员作案例分析。

4. 1 发送函数

    原版本定义了一个局部字节数组,无论在哪种存储模式下编译运行,均将半字变量拆分成两个字节,高字节写入低地址,低字节写入高地址,即直接按大端顺序写入字节数组,保证发送和接收缓冲区的内存表示的一致性,即网络传输的IP报头的一致性。

eip e_ip;

uint8 IpHeadUint8[20];

…… e_ip. TotalLen=(*TxdData). length+20; IpHeadUint8[2]=(e_ip.TotalLen&0xff00)>>8;

IpHeadUint8[3]=e_ip.TotalLen&0x00f;

f …… e_ip.Crc=CreateIpHeadCrc(IpHeadUint8);

IpHeadUint8[10]=(e_ip. Crc&0xff00)>>8;

IpHeadUint8[11]=e_ip.Crc&0x00f;

f …… TxdIpData.DAPTR=IpHeadUnit8;

Send_Ip_To_LLC (&TxdIpData, e_ip. DestId, num);

     笔者的做法是利用共用体类型union ip-rc实现,可以节省局部字节数组空间,大小端情况区别对待,大端模式下,直接写入原值;小端模式下,先写入原值,再对该变量成员作高低字节转换,即最终写入的值不同(与原值字节顺序相反),内存表示却变的相同了(原值的大端顺序)。

union ip_rc {eip e_ip; struct {uint16 wordbuf[10]; } words; };

union ip_rc IpHead; …… IpHead. e_ip. TotalLen=(*TxdData). length+20;

#ifdefLittle_End IpHead. e_ip. TotalLen=swap_ int16( IpHead e_ ip. To- talLen);

#endif …… IpHead. e_ip. Crc=CreateIpHeadCrc_1 ( IpHead. words. wordbuf);

#ifdefLittle_End IpHead. e_ip. Crc=swap_int16(IpHead. e_ip. Crc);

#endif …… TxdIpData. DAPTR=(uint8* ) & IpHead. e_ip; Send_Ip_To_LLC (& TxdIpData, IpHead. e_ip. DestId, num);

4. 2 IP报头校验和计算函数

    不论哪种存储模式下,为了保证校验和结果的惟一性,原版本对传递过来的8位字节数组(内存表示默认为大端顺序)强制按半字16位大端顺序读出及求和。

union w Crc: //类型union w在ip. h中定义,存储模式不同,定义也不同

Crc. dwords=0;

for ( i=0; i<10; i++) Crc. dwords=Crc. dwords+ ((uint 32) Ip[2* i]<<8) + (uint32)Ip[2* i+1];

笔者实现的校验和计算函数更简洁更通用,形参16位半字数组是确定不变的大端字节顺序,大端模式下,读出的IpH[ i]就是原值,直接累加即可;小端模式下,读出IpH[ i]后必须转换高低字节,才是当初写入的原值,然后再累加。

uint32 temp=0;

for ( i=0; i<10, i++)

{

#ifdefBig_End temp=temp+(uint32) IpH[ i];

#endif

#ifdefLittle_End

temp=temp + (uint32) (swap_int16 (IpH[ i]));

#endif }

4. 3 接收函数

     原版本中,传递过来的IP报头是大端顺序字节数组中的数据,在大端模式下读出时,就是正确的原值,在小端环境下编译运行时,内存表示是大端顺序,却按小端顺序读出,结果值与原值高低字节顺序相反,因此,要将读出的结果进行高低字节互换,才能得到正确的原值。

#ifdefBig_End PackedLength=((eip* )RecData)->TotalLen;

#endif #ifdefLittle_End PackedLength=((eip* )RecData)->TotalLen;

Ltemp=PackedLength&0x00f;

f PackedLength=(PackedLength&0xff00)>>8;

PackedLength=PackedLength+(Ltemp<<8);

#endif

而笔者精减了代码,读操作是无论哪种存储模式下都存在的,可以统一出来,之后针对小端模式的特殊情况,进行高低字节转换,变回原值。

PackedLength=((eip* )RecData)->TotalLen;

#ifdefLittle_End PackedLength=swap_int16 (PackedLength));

#endif

通过阅读源码得知, ZLG/IP约定:无论在哪种存储模式的机器上编译运行, IP报头在发送和接收缓冲区中的字节顺序表示为大端字节顺序。

这样的约定有两个好处:

(1)保证了网络通信的数据包报头的字节顺序一致,形成网络字节顺序的标准,当在通信双方的不同的存储模式下读出时,由终端计算机系统自己转换;

(2)方便了报头校验和的计算,不论大小端存储模式,一致按大端 顺序读出,保证了校验和的惟一性。

5 结语

   由上述分析可见,本文所写代码可以节省局部数组空间,代码量更少,函数模块更通用。因此,可以进一步精简ZLG/IP中ICP报头、UDP报头的发送、检验、接收源代码。

---------------------------------------------

什么时候要进行大小端字节序的转换?  

short 或者 long的数据在进行通信的时候最好养成:  1、发送的时候使用:htons(l)  2、接受的时候使用:ntohs(l)  而不要理会两边的通信是否需要这么做~~  当然了一般我都不用int型的数据通信,从来都是字符串通信,发送方利用sprintf组织,接收方利用atoi进行转换~~

(转)大小端模式详解

标签:

原文地址:http://www.cnblogs.com/duguqiuying/p/4616468.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!