大端(BigEndian)与小端(LittleEndian)详解
大端(BigEndian)与小端(LittleEndian)简介
///////////////////////////////////////////////////////
1.你从哪里来?
端模式(Endian)的这个词出自JonathanSwift书写的《格列佛
游记》。这本书根据将鸡蛋敲开的方法不同将所有的人分为两类,从
圆头开始将鸡蛋敲开的人被归为BigEndian,从尖头开始将鸡蛋敲开
的人被归为LittileEndian。小人国的内战就源于吃鸡蛋时是究竟从大
头(Big-Endian)敲开还是从小头(Little-Endian)敲开。在计算机
业BigEndian和LittleEndian也几乎引起一场战争。在计算机业界,
Endian表示数据在存储器中的存放顺序。采用大端方式进行数据存放
符合人类的正常思维,而采用小端方式进行数据存放利于计算机处理。
下文举例说明在计算机中大小端模式的区别。
//////////////////////////////////////////////////////
2.读书百遍其义自见
小端口诀:高高低低->高字节在高地址,低字节在低地址
大端口诀:高低低高->高字节在低地址,低字节在高地址
longtest=0x313233334;
小端机器:
低地址-->高地址
00000010:34333231->4321
大端机器:
低地址-->高地址
00000010:31323334->4321
test变量存储的是的0x10这个地址,
那编译器怎么知道是读四个字节呢?->根据变量test的类型long
可知这个变量占据4个字节.
那编译器怎么读出这个变量test所代表的值呢?->这就根据是
littleendian还是bigendian来读取
所以,小端,其值为0x31323334;大端,其值为0x34333231
htonl(test)的情况:->其值为:0x34333231
小端机器:
00000010:31323334->1234
大端机器:
00000010:34333231->4321
///////////////////////////////////////////////////////////////////
//////////////////
3.拿来主义
ByteEndian是指字节在内存中的组织,所以也称它为Byte
Ordering,或ByteOrder。
对于数据中跨越多个字节的对象,我们必须为它建立这样的约定:
(1)它的地址是多少?
(2)它的字节在内存中是如何组织的?
针对第一个问题,有这样的解释:
对于跨越多个字节的对象,一般它所占的字节都是连续的,它的
地址等于它所占字节最低地址。(链表可能是个例外,但链表的地址可
看作链表头的地址)。
比如:intx,它的地址为0x100。那么它占据了内存中的Ox100,
0x101,0x102,0x103这四个字节(32位系统,所以int占用4个
字节)。
上面只是内存字节组织的一种情况:多字节对象在内存中的组织有
一般有两种约定。考虑一个W位的整数。
它的各位表达如下:[Xw-1,Xw-2,...,X1,X0],它的
MSB(MostSignificantByte,最高有效字节)为[Xw-1,Xw-
2,...Xw-8];
LSB(LeastSignificantByte,最低有效字节)为[X7,X6,...,
X0]。
其余的字节位于MSB,LSB之间。
LSB和MSB谁位于内存的最低地址,即谁代表该对象的地址?
这就引出了大端(BigEndian)与小端(LittleEndian)的问题。
如果LSB在MSB前面,既LSB是低地址,则该机器是小端;反
之则是大端。
DEC(DigitalEquipmentCorporation,现在是Compaq公司的
一部分)和Intel的机器(X86平台)一般采用小端。
IBM,Motorola(PowerPC),Sun的机器一般采用大端。
当然,这不代表所有情况。有的CPU即能工作于小端,又能工作
于大端,比如ARM,Alpha,摩托罗拉的PowerPC。具体情形参考
处理器手册。
具体这类CPU是大端还是小端,应该和具体设置有关。
(如,PowerPC支持little-endian字节序,但在默认配置时是
big-endian字节序)
一般来说,大部分用户的操作系统(如windows,FreeBsd,Linux)
是LittleEndian的。少部分,如MACOS,是BigEndian的。
所以说,LittleEndian还是BigEndian与操作系统和芯片类型都
有关系。因此在一个处理器系统中,有可能存在大端和小端模式同时
存在的现象。这一现象为系统的软硬件设计带来了不小的麻烦,这要
求系统设计工程师,必须深入理解大端和小端模式的差别。大端与小
端模式的差别体现在一个处理器的寄存器,指令集,系统总线等各个
层次中。
Linux系统中,你可以在/usr/include/中(包括子目录)查找字
符串BYTE_ORDER(或
_BYTE_ORDER,__BYTE_ORDER),确定其值。BYTE_ORDER中
文称为字节序。这个值一般在endian.h或machine/endian.h文件中
可以找到,有时在feature.h中,不同的操作系统可能有所不同。
【用函数判断系统是BigEndian还是LittleEndian】
enum{FALSE=0,TRUE=!FALSE};
typedefshortBOOL;
BOOLIsBig_Endian()
//如果字节序为big-endian,返回true;
//反之为little-endian,返回fal
{
unsignedshorttest=0x1122;
if(*((unsignedchar*)&test)==0x11)
returnTRUE;
el
returnFALSE;
}//IsBig_Endian()
///////////////////////////////////////////////////////////////////
///////////
可以做个实验
在windows上下如下程序
#include
#include
voidmain(void)
{
shorttest;
FILE*fp;
test=0x3132;//(31ASIIC码的’1’,32ASIIC码的’2’)
if((fp=fopen("c://","wb"))==NULL)
asrt(0);
fwrite(&test,sizeof(short),1,fp);
fclo(fp);
}
然后在C盘下打开文件,可以看见内容是21,而test等于
0x3132,可以明显的看出来x86的字节顺序是低位在前.如果我们把这
段同样的代码放到(big-endian)的机器上执行,那么打出来的文件就是
12.这在本机中使用是没有问题的.但当你把这个文件从一个big-
endian机器复制到一个little-endian机器上时就出现问题了.
如上述例子,我们在big-endian的机器上创建了这个test文件,把
其复制到little-endian的机器上再用fread读到一个short里面,我们
得到的就不再是0x3132而是0x3231了,这样读到的数据就是错误的,
所以在两个字节顺序不一样的机器上传输数据时需要特别小心字节顺
序,理解了字节顺序在可以帮助我们写出移植行更高的代码.
正因为有字节顺序的差别,所以在网络传输的时候定义了所有字节
顺序相关的数据都使用big-endian,BSD的代码中定义了四个宏来处理:
#definentohs(n)//网络字节顺序到主机字节顺序n代表net,
h代表host,s代表short
#definehtons(n)//主机字节顺序到网络字节顺序n代表net,
h代表host,s代表short
#definentohl(n)//网络字节顺序到主机字节顺序n代表net,
h代表host,s代表long
#definehtonl(n)//主机字节顺序到网络字节顺序n代表net,
h代表host,s代表long
举例说明下这其中一个宏的实现:
#definesw16(x)/
((short)(/
(((short)(x)&(short)0x00ffU)<<8)|/
(((short)(x)&(short)0xff00U)>>8)))
这里实现的是一个交换两个字节顺序.其他几个宏类似.
我们改写一下上面的程序
#include
#include
#definesw16(x)/
((short)(/
(((short)(x)&(short)0x00ffU)<<8)|/
(((short)(x)&(short)0xff00U)>>8)))
#definesw32(x)/
((long)(/
(((long)(x)&(long)0x000000ff)<<24)|/
(((long)(x)&(long)0x0000ff00)<<8)|/
(((long)(x)&(long)0x00ff0000)>>8)|/
(((long)(x)&(long)0xff000000)>>24)))
//因为x86下面是低位在前,需要交换一下变成网络字节顺序
#definehtons(x)sw16(x)
#definehtonl(x)sw32(x)
voidmain(void)
{
shorttest;
FILE*fp;
test=htons(0x3132);//(31ASIIC码的’1’,32ASIIC码的’2’)
if((fp=fopen("c://","wb"))==NULL)
asrt(0);
fwrite(&test,sizeof(short),1,fp);
fclo(fp);
}
如果在高字节在前的机器上,由于与网络字节顺序一致,所以我们什
么都不干就可以了,只需要把#definehtons(x)sw16(x)宏替换为
#definehtons(x)(x).
一开始我在理解这个问题时,总在想为什么其他数据不用交换字节
顺序?比如说我们write一块buffer到文件,最后终于想明白了,因为都
是unsignedchar类型一个字节一个字节的写进去,这个顺序是固定的,
不存在字节顺序的问题
【大端(BigEndian)与小端(LittleEndian)简介】
ByteEndian是指字节在内存中的组织,所以也称它为Byte
Ordering,或ByteOrder。
对于数据中跨越多个字节的对象,我们必须为它建立这样的约定:
(1)它的地址是多少?
(2)它的字节在内存中是如何组织的?
针对第一个问题,有这样的解释:
对于跨越多个字节的对象,一般它所占的字节都是连续的,它的
地址等于它所占字节最低地址。(链表可能是个例外,但链表的地址可
看作链表头的地址)。
比如:intx,它的地址为0x100。那么它占据了内存中的Ox100,
0x101,0x102,0x103这四个字节(32位系统,所以int占用4个
字节)。
上面只是内存字节组织的一种情况:多字节对象在内存中的组织有
一般有两种约定。考虑一个W位的整数。
它的各位表达如下:[Xw-1,Xw-2,...,X1,X0],它的
MSB(MostSignificantByte,最高有效字节)为[Xw-1,Xw-
2,...Xw-8];
LSB(LeastSignificantByte,最低有效字节)为[X7,X6,...,
X0]。
其余的字节位于MSB,LSB之间。
LSB和MSB谁位于内存的最低地址,即谁代表该对象的地址?
这就引出了大端(BigEndian)与小端(LittleEndian)的问题。
如果LSB在MSB前面,既LSB是低地址,则该机器是小端;反
之则是大端。
DEC(DigitalEquipmentCorporation,现在是Compaq公司的
一部分)和Intel的机器(X86平台)一般采用小端。
IBM,Motorola(PowerPC),Sun的机器一般采用大端。
当然,这不代表所有情况。有的CPU即能工作于小端,又能工作
于大端,比如ARM,Alpha,摩托罗拉的PowerPC。具体情形参考
处理器手册。
具体这类CPU是大端还是小端,应该和具体设置有关。
(如,PowerPC支持little-endian字节序,但在默认配置时是
big-endian字节序)
一般来说,大部分用户的操作系统(如windows,FreeBsd,Linux)
是LittleEndian的。少部分,如MACOS,是BigEndian的。
所以说,LittleEndian还是BigEndian与操作系统和芯片类型都
有关系。
Linux系统中,你可以在/usr/include/中(包括子目录)查找字
符串BYTE_ORDER(或
_BYTE_ORDER,__BYTE_ORDER),确定其值。BYTE_ORDER中
文称为字节序。这个值一般在endian.h或machine/endian.h文件中
可以找到,有时在feature.h中,不同的操作系统可能有所不同。
bigendian是指低地址存放最高有效字节(MSB),而little
endian则是低地址存放最低有效字节(LSB)。
用文字说明可能比较抽象,下面用图像加以说明。比如数字
0x12345678在两种不同字节序CPU中的存储顺序如下所示:
BigEndian
低地址高地址
----------------------------------------->
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|12|34|56|78|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
LittleEndian
低地址高地址
----------------------------------------->
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|78|56|34|12|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
从上面两图可以看出,采用bigendian方式存储数据是符合我们
人类的思维习惯的.
为什么要注意字节序的问题呢?你可能这么问。当然,如果你写
的程序只在单机环境下面运行,并且不和别人的程序打交道,那么你
完全可以忽略字节序的存在。但是,如果你的程序要跟别人的程序产
生交互呢?在这里我想说说两种语言。C/C++语言编写的程序里数据
存储顺序是跟编译平台所在的CPU相关的,而J***A编写的程序则唯
一采用bigendian方式来存储数据。试想,如果你用C/C++语言在
x86平台下编写的程序跟别人的J***A程序互通时会产生什么结果?就
拿上面的0x12345678来说,你的程序传递给别人的一个数据,将指
向0x12345678的指针传给了J***A程序,由于J***A采取bigendian
方式存储数据,很自然的它会将你的数据翻译为0x78563412。什么?
竟然变成另外一个数字了?是的,就是这种后果。因此,在你的C程
序传给J***A程序之前有必要进行字节序的转换工作。
无独有偶,所有网络协议也都是采用bigendian的方式来传输数
据的。所以有时我们也会把bigendian方式称之为网络字节序。当两
台采用不同字节序的主机通信时,在发送数据之前都必须经过字节序
的转换成为网络字节序后再进行传输。ANSIC中提供了下面四个转换
字节序的宏。
·BE和LE一文的补完
我在8月9号的《BigEndian和LittleEndian》一文中谈了字节
序的问题,原文见上面的超级链接。可是有朋友仍然会问,CPU存储
一个字节的数据时其字节内的8个比特之间的顺序是否也有big
endian和littleendian之分?或者说是否有比特序的不同?
实际上,这个比特序是同样存在的。下面以数字0xB4
(10110100)用图加以说明。
BigEndian
msblsb
---------------------------------------------->
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|1|0|1|1|0|1|0|0|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
LittleEndian
lsbmsb
---------------------------------------------->
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|0|0|1|0|1|1|0|1|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
实际上,由于CPU存储数据操作的最小单位是一个字节,其内部
的比特序是什么样对我们的程序来说是一个黑盒子。也就是说,你给
我一个指向0xB4这个数的指针,对于bigendian方式的CPU来说,
它是从左往右依次读取这个数的8个比特;而对于littleendian方式
的CPU来说,则正好相反,是从右往左依次读取这个数的8个比特。
而我们的程序通过这个指针访问后得到的数就是0xB4,字节内部的比
特序对于程序来说是不可见的,其实这点对于单机上的字节序来说也
是一样的。
那可能有人又会问,如果是网络传输呢?会不会出问题?是不是
也要通过什么函数转换一下比特序?嗯,这个问题提得很好。假设
littleendian方式的CPU要传给bigendian方式CPU一个字节的话,
其本身在传输之前会在本地就读出这个8比特的数,然后再按照网络
字节序的顺序来传输这8个比特,这样的话到了接收端不会出现任何
问题。而假如要传输一个32比特的数的话,由于这个数在littel
endian方存储时占了4个字节,而网络传输是以字节为单位进行的,
littleendian方的CPU读出第一个字节后发送,实际上这个字节是原
数的LSB,到了接收方反倒成了MSB从而发生混乱。
【用函数判断系统是BigEndian还是LittleEndian】
boolIsBig_Endian()
//如果字节序为big-endian,返回true;
//反之为little-endian,返回fal
{
unsignedshorttest=0x1122;
if(*((unsignedchar*)&test)==0x11)
returnTRUE;
el
returnFALSE;
}//IsBig_Endian()
字节序问题--大端法小端法收藏
一、字节序定义
字节序,顾名思义字节的顺序,再多说两句就是大于一个字节类
型的数据在内存中的存放顺序(一个字节的数据当然就无需谈顺序的问
题了)。
其实大部分人在实际的开发中都很少会直接和字节序打交道。唯
有在跨平台以及网络程序中字节序才是一个应该被考虑的问题。
在所有的介绍字节序的文章中都会提到字节序分为两类:Big-
Endian和Little-Endian。引用标准的Big-Endian和Little-Endian的
定义如下:
a)Little-Endian就是低位字节排放在内存的低地址端,高位字节
排放在内存的高地址端。
b)Big-Endian就是高位字节排放在内存的低地址端,低位字节排
放在内存的高地址端。
c)网络字节序:4个字节的32bit值以下面的次序传输:首先是
0~7bit,其次8~15bit,然后16~23bit,最后是24~31bit。这种
传输次序称作大端字节序。由于TCP/IP首部中所有的二进制整数在网
络中传输时都要求以这种次序,因此它又称作网络字节序。比如,以
太网头部中2字节的“以太网帧类型”,表示后面数据的类型。对于
ARP请求或应答的以太网帧类型来说,在网络传输时,发送的顺序是
0x08,0x06。在内存中的映象如下图所示:
栈底(高地址)
---------------
0x06--低位
0x08--高位
---------------
栈顶(低地址)
该字段的值为0x0806。按照大端方式存放在内存中。
二、高/低地址与高低字节
首先我们要知道我们C程序映像中内存的空间布局情况:在《C专
家编程》中或者《Unix环境高级编程》中有关于内存空间布局情况的
说明,大致如下图:
-----------------------最高内存地址0xffffffff
|栈底
.
.栈
.
栈顶
-----------------------
|
|
/|/
NULL(空洞)
/|/
|
|
-----------------------
堆
-----------------------
未初始化的数据
----------------(统称数据段)
初始化的数据
-----------------------
正文段(代码段)
-----------------------最低内存地址0x00000000
以上图为例如果我们在栈上分配一个unsignedcharbuf[4],那
么这个数组变量在栈上是如何布局的呢[注1]?看下图:
栈底(高地址)
----------
buf[3]
buf[2]
buf[1]
buf[0]
----------
栈顶(低地址)
现在我们弄清了高低地址,接着来弄清高/低字节,如果我们有一
个32位无符号整型0x12345678(呵呵,恰好是把上面的那4个字节
buf看成一个整型),那么高位是什么,低位又是什么呢?其实很简单。
在十进制中我们都说靠左边的是高位,靠右边的是低位,在其他进制
也是如此。就拿0x12345678来说,从高位到低位的字节依次是0x12、
0x34、0x56和0x78。
高低地址和高低字节都弄清了。我们再来回顾一下Big-Endian和
Little-Endian的定义,并用图示说明两种字节序:
以unsignedintvalue=0x12345678为例,分别看看在两种字
节序下其存储情况,我们可以用unsignedcharbuf[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)--低位
---------------
栈顶(低地址)
在现有的平台上Intel的X86采用的是Little-Endian,而像Sun
的SPARC采用的就是Big-Endian。
三、例子
嵌入式系统开发者应该对Little-endian和Big-endian模式非常
了解。采用Little-endian模式的CPU对操作数的存放方式是从低字
节到高字节,而Big-endian模式对操作数的存放方式是从高字节到低
字节。
例如,16bit宽的数0x1234在Little-endian模式CPU内存中的
存放方式(假设从地址0x4000开始存放)为:
内存地址存放内容
0x40010x12
0x40000x34
而在Big-endian模式CPU内存中的存放方式则为:
内存地址存放内容
0x40010x34
0x40000x12
32bit宽的数0x12345678在Little-endian模式CPU内存中的存
放方式(假设从地址0x4000开始存放)为:
内存地址存放内容
0x40030x12
0x40020x34
0x40010x56
0x40000x78
而在Big-endian模式CPU内存中的存放方式则为:
内存地址存放内容
0x40030x78
0x40020x56
0x40010x34
0x40000x12
三、例子
测试平台:SunSPARCSolaris9和IntelX86Solaris9
我们的例子是这样的:在使用不同字节序的平台上使用相同的程
序读取同一个二进制文件的内容。
生成二进制文件的程序如下:
/*gen_binary.c*/
intmain(){
FILE*fp=NULL;
intvalue=0x12345678;
intrv=0;
fp=fopen("","wb");
if(fp==NULL){
printf("fopenerror/n");
return-1;
}
rv=fwrite(&value,sizeof(value),1,fp);
if(rv!=1){
printf("fwriteerror/n");
return-1;
}
fclo(fp);
return0;
}
读取二进制文件的程序如下:
intmain(){
intvalue=0;
FILE*fp=NULL;
intrv=0;
unsignedcharbuf[4];
fp=fopen("","rb");
if(fp==NULL){
printf("fopenerror/n");
return-1;
}
rv=fread(buf,sizeof(unsignedchar),4,fp);
if(rv!=4){
printf("freaderror/n");
return-1;
}
memcpy(&value,buf,4);//orvalue=*((int*)buf);
printf("thevalueis%x/n",value);
fclo(fp);
return0;
}
测试过程:
(1)在SPARC平台下生成文件
在SPARC平台下读取文件的结果:
thevalueis12345678
在X86平台下读取文件的结果:
thevalueis78563412
(1)在X86平台下生成文件
在SPARC平台下读取文件的结果:
thevalueis78563412
在X86平台下读取文件的结果:
thevalueis12345678
[注1]
buf[4]在栈的布局我也是通过例子程序得到的:
intmain(){
unsignedcharbuf[4];
printf("thebuf[0]addris%x/n",buf);
printf("thebuf[1]addris%x/n",&buf[1]);
return0;
}
output:
SPARC平台:
thebuf[0]addrisffbff788
thebuf[1]addrisffbff789
X86平台:
thebuf[0]addris8047ae4
thebuf[1]addris8047ae5
两个平台都是buf[x]所在地址高于buf[y](x>y)。
如何判断系统是BigEndian还是LittleEndian?
在/usr/include/中(包括子目录)查找字符串BYTE_ORDER(或
_BYTE_ORDER,__BYTE_ORDER),确定其值。这个值一般在
endian.h或machine/endian.h文件中可以找到,有时在feature.h中,
不同的操作系统可能有所不同。一般来说,LittleEndian系统
BYTE_ORDER(或_BYTE_ORDER,__BYTE_ORDER)为1234,Big
Endian系统为4321。大部分用户的操作系统(如windows,
FreeBsd,Linux)是LittleEndian的。少部分,如MACOS,是Big
Endian的。本质上说,LittleEndian还是BigEndian与操作系统和
芯片类型都有关系。
ProcessorOSOrder
x86(Intel,AMD,…)Alllittle-endian
DECAlphaAlllittle-endian
HP-PANTlittle-endian
HP-PAUNIXbig-endian
SUNSPARCAll?big-endian
MIPSNTlittle-endian
MIPSUNIXbig-endian
PowerPCNTlittle-endian
PowerPCnon-NTbig-endian
RS/6000UNIXbig-endian
Motorolam68kAllbig-endian
本文发布于:2022-12-29 23:04:49,感谢您对本站的认可!
本文链接:http://www.wtabcd.cn/fanwen/fan/90/55967.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
留言与评论(共有 0 条评论) |