位段
目录
1、位段成员必须是整型家族的(int ,unsigned int ,char 等)。
C语言中有很多的自定义类型,我在以前的博客中已经讲过了结构体和结构体的内存对齐,现在我们来讲另一个自定义类型,就是位段。
一、定义
位段,C语言允许在一个结构体中以比特位为单位来指定其成员所占内存长度,这种以位为单位的成员称为“位段”或称“位域”( bit field) 。利用位段能够用较少的位数存储数据。
可以看出位段和结构体很像,下面我们引入一段代码片段来看一下位段到底长什么样子。
struct A{
int a : 2;//成员a占2个比特位
int b : 5;//成员b占5个比特位
int c : 10;//成员c占10个比特位
int d : 30;//成员d占30个比特位
};
二、注意事项
1、位段成员必须是整型家族的(int ,unsigned int ,char 等)。
可以看见,这里如果是float类型的成员,编译器会直接报错。
整型家族的数组也不行哦,编译器也会报错。
2、位段成员名后可跟冒号加一个数字来表示它占有几个比特位
正如我们在 一、定义 时的代码片段,但是其实一个位段的部分成员也可以不加冒号和数字(如果全部成员都不加冒号和数字了它就变成结构体了)。
例如上图,我们也是不会错的。
三、位段的内存分配
在window环境下的VS编译器中,位段的内存分配是按照这种规则的:根据首个成员变量的类型分配相应字节的内存(例如:首个成员是 int 类型的,则分配4个字节即32个比特位的空间大小),然后根据成员的实际大小(也就是:后面的数字)来填充这个已经分配的空间,若剩余的空间不够了,则继续根据成员类型依次分配内存并且填充(分配内存时要注意内存对齐,如果不知道什么是内存对齐,可以看我以前写的博客,填充时不用对齐),直到该位段的最后一个成员,需要注意的是,整个位段的大小是成员类型大小中最大值的整数倍。
上面的概念过于复杂,我们来看一个例子:
第一步,分配,根据第一个成员的类型分配4字节即32位大小的内存;
第二步,填充,因为a,b,c成员的大小加起来为27位,而d成员为6位,所以这块被分配的内存只能填充a,b,c(这里的填充是反向填充,即从该区域的最后端开始依次填充a,b,c);
第三步,分配,根据接下来的成员也就是 char 型的 d 分配一个字节8个比特位的空间,根据内存对齐判断,该空间紧贴上一个分配的空间即可;
第四步,填充,这块空间只够填充成员 d ;
第五步,分配,并判断内存对齐,应根据 int 型变量 e 分配4个字节的空间,但根据内存对齐这段被分配的空间不能紧挨着成员 d ,应该间隔3个字节再分配(对齐到该成员对齐数整数倍的位置上);
第六步,填充,这个空间只够填充成员 e 占用其中的30位;
第七步,根据成员 f 的 char 型分配一个字节的空间,根据内存对齐规则,该区域可以紧贴上一个区域,然后将最后一个成员填充进去;
第八步,根据内存对齐规则的最后一条,也就是整个位段的大小是成员类型大小中最大值的整数倍,所以我们必须还要在余出3个字节;
所以,位段A的内存分布情况为下图:
sizeof(struct A)=5+3+4+1+3=16字节。
下面来验证一下:
四、具体空间开辟(截断)
看一个具体的例子:
struct S{
char a : 3;
char b : 4;
char c : 5;
char d : 4;
};
struct S s = { 0 };
s.a = 10;
s.b = 12;
s.c = 3;
s.d = 4;
由于位段的成员的大小被重新分配,例如成员 a 占3个比特位,它的范围应该是 0 到 7 ,而这里给 a 赋值为10,应该如何处理呢?这里的操作我称作为截断
请看下面我画出的内存图,更为直观
验证:这是VS中内存的部分
和我们的分析一致。
五、位段的跨平台问题
位段在不同的平台上有不同的规则:
1、int 位段被当成有符号数还是无符号数是不确定的。
2、位段中的成员从左到右分配还是从右到左分配是不确定的。
3、当一个结构包含两个位段,第二个位段成员比较大,无法容纳第一个位段剩余的位时,是舍弃剩余的位还是利用,也是不确定的。
所以跟结构体相比,位段更节省空间,但是有跨平台的问题。
这就是今天和大家分享的内容了,希望大家一起提高,共同进步!!!