当前位置: 首页 > 科技观察

下面说说如何理解字节序

时间:2023-03-12 09:24:54 科技观察

计算机只能理解由0和1组成的二进制数据。一个位的值是0或1。八个这样的位组成一个字节。计算机通过字节可以表示一些复杂的数据,比如:音频、视频等,有的数据可以用一个字节来表示,比如英文字符,有的数据需要用多个字节来表示,比如:中文人物。对于多字节数据,在存储的时候会出现字节序问题,即什么是字节序?字节序列是计算机存储多字节数据的方式。目前的方法有:big-endianbyteorder和little-endianbyteorder。字节顺序主要针对多字节数据类型。,如short,int等big-endian高位字节存放在内存的低位地址,低位字节存放在内存的高位地址,little-endian高位字节存放在内存的高地址,低位字节段存储在内存的低地址如何理解字节顺序我们平时写数字和读数字的习惯是从左到右,所以最左边的字节被认为是最高的字节,最右边的字节被认为是最右边的字节。最低字节,从左到右,表示从高字节到低字节例如:对于0x01020304,它的bigendian和littleendian字节序在内存中的布局如下图0x01020304共四个中人们习惯的读取顺序,0x01在左,属于高字节,0x04在右,属于低字节。可以按照big-endian顺序的规则来存储:高位字节存储在内存的低地址,所以高位字节中的0x01存储在地址0x00000007,而下一个高位字节0x02存放在倒数第二低的地址0x00000008,剩下的两个字节0x03和0x04分别存放在地址0x00000009和0x0000000A,最后的resultis0x01020304little-endianbytes顺序刚好和bigendian相反,表示高位字节存放在内存的高地址,所以高位字节0x01存放在地址0x0000000A,次高字节0x02存放在次高地址0x00000009,其余0x03和0x04分别存放在0x00000008和0x00000007,以及最后的结果是0x04030201。从上图可以看出,对于同样的数据,bigendian和little-endian内存是不同的布局和大端内存布局存储形式更符合人们平时的书写和阅读习惯。可能有人会疑惑,为什么会有字节序:既然大端字节序更符合人们的阅读习惯,为什么不全部采用大端方式,这样就不会有字节序的问题了?事实上,如果所有平台都使用相同的存储顺序,就不会有字节序这样的东西。早期的CPU只有几千个逻辑门,小端法可以更有效地使用逻辑电路,所以很多计算机内部的计算都是采用little-endian的方式,这种方式一直保留到现在。另外,字节序与CPU架构有关,不同厂商设计的规范可能不同。比如Intel的x86就是little-endian方式,而IBM的PowerPC采用big-endian的方式,更符合人们的阅读习惯,所以大部分网络传输和文件存储都是big-endian。一般情况下,little-endian主要是在计算机内部使用,而big-endian则是在外部使用时如何处理计算机的字节序计算机读取数据时不区分字节序,总是从低地址开始内存到高地址,按字节读取下图示例数据0x0102大端和小端内存布局和CPU读取内存的顺序可以从上图看出。对于big-endian字节顺序,高位字节存放在内存的低地址,即计算机从内存中读取的第一个字。section为高位字节,little-endian字节序刚好相反。低位字节存放在内存的低地址,从内存中读取的第一个字节就是低位字节。计算机只需要在读取数据时进行区分即可。字节序以上图big-endian方式(第一张图)为例,内存地址0x00000007存放的数据为0x01,地址0x00000008存放的数据是0x02。如果以端到端的方式读取,地址为0x00000007的数据0x01会放在高位字节,地址为0x00000008的数据会放在0x02的低位字节.最后这两个字节的数据为0x0102如果是小端读取,地址为0x00000007的数据0x01会放在低位字节,0x000000的数据08将放在0x02的高位字节中。最后,这两个字节的数据是0x0201网络字节序。所有的协议都是人为编写的,bigendian对人的阅读更友好。因此,IEEE标准协会规定网络协议除非另有说明,否则使用bigendian字节顺序。TCP/IP就是这种情况。记得我们在写网络程序的时候,在connect函数的实参中传入了端口号,传入之前需要先调用htons函数将其转换为网络字节序,即大端字节序。以下是部分代码示例structsockaddr_inaddr;addr.sin_family=AF_INET;addr.sin_addr.s_addr=inet_addr("192.168.1.10");addr.sin_port=htons(5000);connect(clientfd,(structsockaddr*)&addr,sizeof(addr)))上面红色的htons函数的作用是将端口号从主机字节序列转换为网络字节序列,和网络字符大多数时候,序列是固定的大端,但不同的机器有不同的主机序列。如果已经是big-endian,则调用htons函数,返回值与实参相同。如果是smallendian,结果会转成big-endian,具体取值会不一样如何判断endian的大小上面说的hostbyteorder,如何知道当前机器是不是big-字节序还是小字节序?因为操作系统必须适应所有类型的CPU,所以对于操作系统来说,big-endian和little-endian都是支持的。为了方便程序判断当前平台是big-endian还是little-endian,Linux下的glibc库提供了如下宏定义BIG_ENDIAN#big-endianLITTLE_ENDIAN#LittleendianorderBYTE_ORDER#Byteorder以下是测试代码test.c文件#includeintmain(intargc,char*argv[]){if(BYTE_ORDER==BIG_ENDIAN){printf("bigendian...\n");}else{printf("littleendian...\n");}}执行gcc-g-otesttest.c命令编译运行测试程序,结果如下:[root@localhosttest]#./testlittleendian...由此我们可以知道当前平台是little-endian除了使用上面的方法,我们还可以根据big-endian和little-endian的特点,编写代码获取并修改test.c文件,内容如下#includeintmain(intargc,char*argv[]){union{unsignedshorti;charch[2];}un;un.i=0x0102;if(0x01==un.ch[0]){printf("bigendian...\n");}else{printf("littleendian...\n");}}编译运行,结果如下:[root@localhosttest]#./testlittleendian...可见无论是通过系统库提供的宏判断还是通过封装接口来判断机器的字节序都是可行的。最后,如果你想了解LITTLE_ENDIAN、BIG_ENDIAN、BYTE_ORDER宏定义的细节,你可以查看glibc源码,它们在glibc-2.17\string\endian.h和glibc-2.17\sysdeps\x86\bits\endian中。h文件注意:不同版本的glibc源码,具体位置可能不同。我使用的是glibc-2.17版本。big-endian和small-endian的转换熟悉big-endian和little-endian的特点。它们之间的转换很简单。对于两个字节,每个字节的值保持不变,交换字节位置,如果字节多,则最低字节与最高字节交换,次低字节与次高字节交换,直到所有字节交换完毕。例如:下面是小端转大端的伪代码#littleendian转大端假设:ch和i都是小端charch[2];积分=0;#x是大端x=ch[1]<<8|ch[0]#y是大端字节序y=((i&0xff000000)>>24)|((i&0x00ff0000)>>8)|((i&0x0000ff00)<<8)|((i&0x000000ff)<<24)变量说明i的字节序转换:按照从左到右的顺序,将i的第一个字节右移3个字节(24位),第二个字节右移1个字节(8位),第二个字节为右边1个字节(8位)。三个字节左移1字节(8位),第四个字节左移3字节(24位),最后合并。在实际程序处理中,应该没有字节序问题,只有“网络字节序”和“主机字节序”,当需要转换字节序时,使用ntohl、ntohs、htonl、htons等函数ntohl#uint32typenetworksequencetohostsequencehtonl#uint32typenetworksequencetohostsequencentohs#uint16typenetworksequencetohostsequencehtons#uint16typehostsequencetonetworksequence当涉及到字节序相关的问题时,需要花一些时间是时候弄清楚了