大端字节(big-endian)和小端字节(little-endian):到底哪些地方要注意字节顺序

引起我对这个问题的思考的原因是因为unicode的编码格式UTF-8,UTF-8是没有字节顺序的。这个编码格式会把unicode字符集中的一个字符编码成1个或者多个字节。而UTF-8编码格式是经常用于网络传输的格式。如:XML文档大多数都是用的UTF-8格式存储的,这样利于网络传输。那么问题来了既然UTF-8把一个字符可能编码成了2个或3个字节,代表这个字符的2个或3个字节的编码为什么没有字节顺序。

下面我们再来看一个问题:

	typedef struct _ALM_EVENT
	{
		UINT16   ID;	
		UINT16   Type;
		INT8 	 Level;
	} ALM_EVENT, *PALM_EVENT;
如果在小端字节的电脑上存储了上述结构体,其内存数据如下(按照地址从小到大的顺序依次列出各个字节):

【0x01,0x02,0x03,0x04,0x05】

那么把这段字节流发送到大端字节的电脑上的时候,如果要正确的把这个结构体解析出来的话,要把这段字节流的顺序变成如下的顺序:

【0x02,0x01,0x04,0x03,0x05】
这里我当时有个疑问,为什么不是整个结构体的字节顺序都要颠倒过来变成如下的字节顺序:

【0x05,0x04,0x03,0x02,0x01】

而是仅仅只把其中的两个UINT16变量的内部字节顺序改变了就行。

经过思考发现,只有多个字节的各个位(bit)是有关系的,或者说要解析出这个多字节时,它的各个位(bit)是要按照一定的顺序排列的。

像UINT16这种2个字节的编码格式:0111000010001111,要把这个16位的编码放在内存里的话是要2个字节的,而这2个字节里的各个位(bit)要按照0111000010001111(从高位到底位)这个顺序来排列和解析。也就是说其实UINT16这个数据是不以字节区分的,它的最小单元为位(bit),只不过它在内存中占用的空间大小为2个字节(内存空间最小的分配单元为字节)。

由此可以得出一个结论:如果编码后的2进制流是大于8位(bit)的,并且整个2进制流是有高低位之分的,解析该2进制流时是需要把各个bit按照一定的顺序排列的,那么存储在内存里的这个多字节的编码就是有字节顺序的(也就是说字节有高字节和低字节之分),存储和解析时就是需要区分大端和小端的。

一般情况下,在内存里各个字节之间有高低位之分的好像目前接触到的就只有内置的各种整形类型了,如:int,uint,short,ushort等等。

现在就可以解释前面提出的问题了,为什么不需要把整个结构体的字节顺序都颠倒过来。因为结构体只是代表一段内存的布局。如上的结构体只是告诉我们,头两个字节是UINT16,3和4两个字节是存储的UINT16,最后一个字节是存储的INT8,而这3个数据类型之间没有任何关系,他们的各个bit之间没有任何关系,这3个数据是相互独立的。

所以按照地址从小到大的顺序的话,这个结构体的3个变量的顺序是不会变的(相对地址没变,布局没变)。


接下来就说说UTF-8为什么字符编码成了多个字节是没有字节顺序的。

UTF-8以字节为单位对Unicode进行编码。从Unicode到UTF-8的编码方式如下:
Unicode编码(十六进制) 
UTF-8 字节流(二进制)
00000000 - 0000007F
0xxxxxxx
00000080 - 000007FF
110xxxxx 10xxxxxx
00000800 - 0000FFFF
1110xxxx 10xxxxxx 10xxxxxx
00010000 - 001FFFFF
11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
00200000 - 03FFFFFF 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
04000000 - 7FFFFFFF 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
UTF-8的特点是对不同范围的字符使用不同长度的编码。对于0x00-0x7F之间的字符,UTF-8编码与 ASCII编码完全相同。UTF-8编码的最大长度是6个字节。从上表可以看出,6字节模板有31个x,即可以容纳31位二进制数字。Unicode的最大码位0x7FFFFFFF也只有31位。
例1:“汉”字的Unicode编码是0x6C49。0x6C49在0x0800-0xFFFF之间,使用用3字节模板了:1110xxxx 10xxxxxx 10xxxxxx。将0x6C49写成二进制是:0110 1100 0100 1001, 用这个比特流依次代替模板中的x,得到:11100110 10110001 10001001,即E6 B1 89。
例2:Unicode编码0x20C30在0x010000-0x10FFFF之间,使用用4字节模板了:11110xxx 10xxxxxx 10xxxxxx 10xxxxxx。将0x20C30写成21位二进制数字(不足21位就在前面补0):0 0010 0000 1100 0011 0000,用这个比特流依次代替模板中的x,得到:11110000 10100000 10110000 10110000,即F0 A0 B0 B0。
从上面的描述可以看出,UTF-8编码成的多字节之间的各个字节是独立的,要解析他们时,是从各个字节入手,而不是整个多字节组成的bit流。也可以这样说,各个字节里的模板bit(11110xxx 10xxxxxx 10xxxxxx 10xxxxxx)就已经告诉了我们该字节里存储的数据位是高位还是低位了。在读取这个字节时通过模板位就能知道模板位后面存储的数据位是高位还是低位,而不是根据计算机的大端还是小端的字节序来判断的。如xxxxxxxxxxxxxxxx这样的16位的bit流(内存中的)要解析成一个数字,我们就无法知道哪个bit是最高位,哪个bit是最低位了,这是要根据存储该数据的计算机的硬件结构(CPU)来确定的,也就是说这样的编码是要知道计算机是大端还是小端才能解析的,这时就与字节顺序有关了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值