大小端:字节序与比特序

前言


前两天被问到一个关于大小端的问题,很久没接触,回答的支支吾吾,说到底还是自己对这个了解的不彻底,今天补上。


大小端


这里明确下,大小端包括字节序和比特序。他们的概念其实也是类似的,区别就如同他们名字,最小排序单位分别是字节与比特。如果不是做芯片或者网络、通信协议,搞计算机的一般都不涉及也不关心比特序。

关于大小字节序或比特序的定义,这里不多做说明。记住一句话,“高高低低是小端“就够了,即高位字节(比特)在高地址,低位字节(比特)在低地址。

对于比特序来说,比特字段应该以字节为边界,这里面就涉及一个关于位结构(又称 位域 )的问题。

一般位结构的定义在协议报头结构体中很常见,比如TCP,IP报头,Linux中tcp.h中报头定义如下:
struct tcphdr {
	__be16	source;
	__be16	dest;
	__be32	seq;
	__be32	ack_seq;
#if defined(__LITTLE_ENDIAN_BITFIELD)
	__u16	res1:4,
		doff:4,
		fin:1,
		syn:1,
		rst:1,
		psh:1,
		ack:1,
		urg:1,
		ece:1,
		cwr:1;
#elif defined(__BIG_ENDIAN_BITFIELD)
	__u16	doff:4,
		res1:4,
		cwr:1,
		ece:1,
		urg:1,
		ack:1,
		psh:1,
		rst:1,
		syn:1,
		fin:1;
#else
#error	"Adjust your  defines"
#endif	
	__be16	window;
	__sum16	check;
	__be16	urg_ptr;
};
通过大小端中位成员的对比,看以看出些端倪了,以下对位结构补充下说明,自己以后也能多注意下。

注意说明:
  • 位域边界以字节为单位,位成员应该存储在一个字节内,不能跨多个字节;
struct testA {
	unsigned char aa : 3;
	unsigned char bb : 7;    // bb成员紧接aa后,这样将超出一个字节,错误
}
  • 位域不能越过它本身的数据类型范围;
  • 位成员可以为无名成员,此时该成员充当填充或调整位置来使用;
struct testB {
	unsigned char aa : 3;
	unsigned char : 2;       // 此处意思4、5位不能使用
	unsigned char bb : 3;    
}

struct testC {
	unsigned char aa : 3;
	unsigned char : 0;       // 此处4-8位填充0
	unsigned char bb : 3;    // bb成员从下一个字节开始
}

然后,回到刚才TCP报头结构的定义。这里有个C语言的知识点,需要说明下:ANSI-C标准中的struct结构里面,结构成员从低地址开始,亦即排在前面的成员地址比后面的成员地址低下。同样的道理,对于位成员来说,他们在一个字节内从低位开始。(在Linux下,直接打印位成员的地址,编译是会报错的,错误显示:cannot take address of bit-field "XXX" ,这里可以利用union的方式去看,网上实例很多)。

也就是因为这个原理,在上面TCP报头定义中,就必须按照大小端的要求,重新定义位成员的在结构中顺序。这里还需要注意一点,无论大端小端,在一个给定地址上,机器显示给我们的值一定是一样的。比如上面TCP结构中,res1和doff两个字段成员组成一个字节(仅仅用以举例子说明),假设该字节的内容是0x12,小端格式中,低址res1中值为0x2,高址doff值为0x1;大端格式中,低址doff值为0x1,高址res1值为0x2,也就体现了结构定义成员顺序的意义。但是,无论如何,我们看到的值都是0x12,不可能存在0x21或者别的值。这也就是不涉及芯片或者协议的开发人员不需要关心比特序的道理了。

不知道说的明不明白,静下来想一想很简单,切忌浮躁与想当然~



你可能感兴趣的:(Linux网络编程)