比特域是一种由结构体扩展而来的数据结构形式,也成为位域或者占符位。定义为:
struct card{
unsigned int pips:4; //占据4个位的长度
unsigned int suit:2; //占据2个位长度
unsigned char kq:2; //占据两个位长度
}
跟在变量声明后面的非负正常数是该数据所占据的比特宽度。
比特宽度最多是机器字的长度,对于PC机和Windows操作系统(32位)而言,这个值是32。
一个32位的int型数据可以存放的数据范围是极大的,在我们的程序中往往并不需要这样大的表示范围,因此,使用比特域把数据压缩到一定的表示范围内,可以有效的节省程序所需要的内存量,这在系统内存容量极为有限的嵌入式系统开发中是很重要的!
在上面的结构中,card变量拥有一个4比特的域pips来存贮0--15的非负整数,一个2比特的域suit来存贮0--3以及一个2比特的域kq来存储两个字符。
实际上,上面定义中的int和char在很多情况下也是可以省略掉的,这样在比特域中所定义的数据结构实际上就与这些基本类型是无关的,只是表示一定区域的一些有符号整形数或者无符号整形数。因此,在声明变量的时候unsigned或者signed是必须注明的,因为编译器需要确切的知道所定义的变量的精确取值范围。
但是,应该注意的是:不同的编译器对于比特域如何分配是有不同定义的,而且使用比特域组织的程序往往可移植性不好,因此,除非是确实需要节省内存,否则比特域(包括联合)最好不要使用。
下面讨论一下带有比特域的结构体的所占空间的大小。
struct A {
char a : 3;
char b : 5;
};
对于连续的声明类型大小相同的比特域来讲,如果它们申请的宽度加起来不超过它们声明的类型大小,那么它们可以压缩在一个分配单元中。对于声明类型大小不同的比特与来讲,它们分配在不同的分配单元中。
上面结构体A中,a
和
b
的类型(char)长度都为1字节,因此可以考虑合并。它们加起来为8比特(char类型所占字节数为1,1字节等于8位),可以放在一个字节中,由于
A
的对齐模数为1,因此不需在后面再填充数据,所以
A
的大小为1字节。
struct B {
char a : 4;
char b : 5;
};
对于结构体B中,a
和
b
的类型长度都为1字节,因此可以考虑合并,但跟A不同的是,它们无法放入一个字节中(加起来为9比特),因此它们要放在各自的分配单元中,
a
和
b
之间有4比特的空隙。
B
的大小为2字节。
struct C {
char a : 3;
int b : 2;
char c : 2;
};
对于结构体C中,a和b的类型长度不同,char为1字节、int为4字节,不能进行合并。同样,b、c也不能进行合并。
因此,
a
单独占用1字节,后面填充3字节以使其和
b
对齐,
b
占用4字节,
c
占用1字节。由于
A
的对齐模数是4字节,所以
c
后面要填充3字节,C
的大小为12字节。
下面再考虑匿名比特域的例子。匿名比特域主要目的是在两个比特域之间加入一段空隙。
struct D {
char a : 2;
char : 2;
char b : 2;
};
匿名比特域对对齐规则没有影响,它唯一的不同是无法引用这个域。由于这3个域的类 型大小是一样的,且可以放在一个字节之中,因此可以合并,D的大小为1字节。
下面再考虑匿名比特域宽度为0的,它的作用是使它后面的比特域对齐到下个比特域的对齐模数上,也就是说,不允许它跟上一个比特域合并。
尽管这三个比特域的类型一样,但是由于struct E { char a : 2; char : 0; char b : 2; };
a
和b
之间有一个宽度为0的匿名比特域,因此它们不能合并。a
和b
个占用1个字节,所以E
的大小为2字节。