在c语言中提供一个一种叫 “位域” 或者 “位段” 的数据结构。它的存在是为了更加的节省空间。因为在有些实际需求中,并不需要占用一个完整的字节,而只是需要一个或者几个二进制位。比如存在一个开关量时,只有 0 和 1 两种状态,只需要一个二进制位存储即可。
位域的定义如下:
struct 位域结构名{ 位域列表 };
其中位域列表的形式为:类型说明符 位域名:位域长度
例如,
struct test{
int a:3;
int b:2;
int c:6;
};
位域的变量的说明 与结构体的方式一样,有三种方式:先定义后说明、同时定义说明、直接说明。
在声明时,位段成员必须是整形或枚举类型(通常是无符号类型)。
结构中也可以包含无名位域,作为相邻成员之间的填充或调整位置。无名位域无法被引用,它们的内容在运行时是不可预测的。
struct s{
unsigned int a:4; //a存放结构的前4个位中
unsigned int :2; // a后面的2个位填充
unsigned int b:2;
};
位域的定义有如下限制
1、一个位域必须存储在同一个字节中,不能跨两个字节。
比如当一个字节所剩的空间不够下一个位域存储时,则从下一个存储单元的起始位置开始存放;也可以专门让某个位域从下一个存储单元的起始位置开始。
struct s{
unsigned int a:4; //a存放结构的前4个位中
unsigned int :2; // a后面的2个位填充
unsigned int b:6; // 剩余的2位不够存储,从下一个存储单元开始存放
unsigned int :0; //空位域,把该unsigned int剩余空间自动全部填充0
unsigned int c:5; //从下一个单元存储,也即是从下一个 unsigned int 开始
}ss;
在这个位域中,a 占第一个字节的前 4 位,a 后面 2 位进行填充,第一个字节剩余的 2 位不够 b 存储,因此 b 从下一个字节开始存储,b 后面的位域填充 0,c 从下一个单元存储,该下一个单元指的是下一个 unsigned int,因为空位域 0 会把 b 后面 unsigned int 所剩的空间全部填充 0, 因此 sizeof(ss) = 8。
2、位域可以是无名域,无名域就是类似于 unsigned int : 0; 或者 unsigned int : 2; 虽然两者只是 0 和非 0 的区别,但是作用却是不同的。若无名域的位数为 0,则下一个位域将会强制从下一个单元开始(这里的一个单元指的不是下一个字节,而是跨过跨过一次数据类型的自然边界);如果无名位域为非 0,则意味着这个无名位域占着空间,不能被使用。
struct test{
unsigned int a:3; //a占3位
unsigned int :0; //对于unsigned int 类型 a 后面的剩余未全填充0
unsigned int b:2; //b 从下一个unsigned int 类型开始
}tt;
在该例子中 a 占 unsigned int类型所占字节的3位,a 后面的无名域会把 a 后面的所有位填充 0,因此对于 unsigned int 类型所占的 4 字节空间,除 a 的 3 位外,其他位均为0,b 从下一个 unsigned int 类型开始,所以 sizeof(tt) 为 8。
struct test{
unsigned int a:3; //a占3位
unsigned int :2; //a后面的2个位填充
unsigned int b:2; // b 跟着无名域后面
}tt;
a后面无名域占 2 位,b跟在无名域后面,所以 a 和 b共占用一个字节。因此sizeof(tt) 为 4。
3、如果相邻位域字段的类型相同,且其位宽之和小于类型的 sizeof 大小,则后面的字段将紧邻前一个字段存储,直到不能容纳为止 。****
struct test {
unsigned char a:3;
unsigned char b:4;
} t;
a 和 b 所占的宽度之和小于 sizeof(unsigned char)大小,因此 a和b共同使用一个字节,所以 sizeof(t) 为 1;
4、如果相邻位域字段的类型相同,但其位宽之和大于类型的 sizeof 大小,则后面的字段将从新的存储单元开始,其偏移量为其类型大小的整数倍 。****
struct test {
unsigned int a:15;
unsigned int b:20; //从下一个unsigned int开始
} t;
由于 a 和 b 宽度之和大于 sizeof(unsigned int)大小,因此b从下一个 unsigned int 处开始,所以 sizeof(t) 为 8。
5、整个结构体的总大小为最宽基本类型成员大小的整数倍。
struct test {
unsigned char a : 3;
unsigned char b : 4;
unsigned char c : 3;
unsigned int d : 5;
} t;
整个结构体的总大小为 unsigned int 类型的整数倍。
6、如果相邻的位域字段的类型不同,则各编译器的具体实现有差异,VC6采取不压缩方式,Dev-C++采取压缩方式。(跟编译器有较大的关系,使用时要慎重,尽量避免)
struct test {
unsigned char a : 3;
unsigned char b : 4;
unsigned char c : 3;
unsigned int d : 5;
} t;
对于该类型,不压缩时,a和b类型相同会占用一个字节,c会占用一个字节,d会单独存放,不会和共占用一个字节,因为整个结构体的总大小为最宽基本类型成员大小的整数倍,因此 sizefo(t)为 8。
若压缩时(gcc下),c 和 d 共占用一个字节,因为整个结构体的总大小为最宽基本类型成员大小的整数倍,因此 sizefo(t)为 4。
7、 如果位域字段之间穿插着非位域字段,则不进行压缩;(不针对所有的编译器,跟编译器有较大的关系,使用时要慎重,尽量避免)
struct test{
unsigned int m: 12;
unsigned int ch;
unsigned int p: 4;
}t;
sizeof(t) 大小为 12。
8、当使用有符号类型来定义位域,并且无意中使用到了正负(有意或者无意)特性时,可能出现不是想要的结果 。
struct test
{
char a : 2;
char b : 3;
char c : 3;
};
struct test t;
// 位域赋值
t.a = 0x3; // 11
t.b = 0x5; // 101
t.c = 0x2; // 010
printf("%d,%d,%d\\n", t.a, t.b, t.c); //结果为 -1, -3, 2
可见,当为域的最高位是 1 的时候,会进行符号扩展,而且这也取决于编译器的实现,因此,为避免此类问题,最好使用无符号类型定义位域。
9、取地址操作符 & 不能应用在位域字段上,因此不存在位域的指针。
10、位域字段不能是静态成员。
11、不能用来指定位数的类型。
若 struct 成员是指针变量类型不能用来指定所占的位数,在 64 位系统中指针固定占 8 字节,在 32 位系统中指针固定占 4 字节。
若 struct 成员是 double 或 float 类型,不能指定位数,否则编译出错,位域类型无效。
审核编辑:刘清