一, 位段的解释
下面是维基百科对位段的解释:
位段(或称“位域”,Bit field)为一种数据结构,可以把数据以位的形式紧凑的储存,并允许程序员对此结构的位进行操作。这种数据结构的好处:
- 可以使数据单元节省储存空间,当程序需要成千上万个数据单元时,这种方法就显得尤为重要。
- 位段可以很方便的访问一个整数值的部分内容从而可以简化程序源代码。
而位域这种数据结构的缺点在于,其内存分配与内存对齐的实现方式依赖于具体的机器和系统,在不同的平台可能有不同的结果,这导致了位段在本质上是不可移植的
二, 位段的声明和使用
- 虽然位段可以决定用多少位来储存数据,但是切不可认为位段就是可以自定义一个数据类型。位段是依赖结构体来实现的,我们可以认为位段是可以将一个盒子里面格子自定义大小。
位段的声明:
struct A { int _a : 2; int _b : 5; int _c : 10; int _d : 30; };
需要注意的是:
- 这里面的数字代表的不是字节,是比特(bit)。
- 位段成员的类型只能是整型家族的,例如:int, unsigned int, signed int, char。
位段的使用:
int main() { struct A a; a._a = 2; a._b = 3; a._c = 5; a._d = 10; return 0; }
相当于实例化后的a里面的不同大小的内存里放入了数据。
三,位段的空间大小计算
因为不同平台上的规则都是不太一样的,计算出来的结果也会有些许差异,以下使用vs2022的x64环境下运行的
例如:
第一个例子:
#include <stdio.h> struct A { int _a : 2;//二进制位 int _b : 5; int _c : 10; int _d : 30; }; int main() { printf("%d", sizeof(struct A)); return 0; }
上面代码的输出结果是8。
- 声明类型是int类型的,所以一开始先开辟4个字节的内存,也就是32bit。
- _a用掉了2bit,还剩下30bit。
- _b用掉了5bit,还剩下25bit。
- _c用掉了10bit,还剩下15bit。
- _d需要30bit的空间,但是预先开辟的空间只剩下15bit,所以我们还需要再开辟一个int大小的空间,之前剩下的15bit的空间选择不使用,_d的30bit全放在第二个空间内。
- 结果为8
第二个例子:
#include <stdio.h> struct B { int _a : 30;//二进制位 int _b : 4; int _d : 32; }; int main() { printf("%d", sizeof(struct B)); return 0; }
- 声明类型是int类型的,所以一开始先开辟4个字节的内存,也就是32bit。
- _a用掉了30bit,还剩下2bit。
- 由于只剩下2bit,_b需要4bit,所以舍弃2bit,再开辟一个32bit空间, _b用掉了4bit,还剩下28bit。
- 由于只剩下28bit,_d需要32bit,所以舍弃28bit,再开辟一个32bit空间,_d用掉了32bit
- 总共开辟了3次int类型的空间,所以结果为12
注意:
大家有没有发现,我们在声明位段的时候,如果定义的是int,那么冒号后面跟上的数字不能超过32,如果定义的是char,那么冒号后面跟上的数字不能超过8。如果超过以后,就会报出以下错误:
其实根据内存对齐原则,如果超出以后,处理器就需要访问两次才能完整的得到数据。所以在定义的时候,应该避免超出应有的内存大小。
四, 位段的内存分配
- 位段分配的内存中的比特位是从左向右使用的,还是从右向左使用的呢?
- 如何证明内存分配剩余的比特位不够使用时,是继续使用还是浪费掉呢?
接下来我们分析:
用例代码:
#include <stdio.h> struct A { char _a : 3; char _b : 4; char _c : 5; char _d : 4; }; int main() { struct A a = {0}; a._a = 10; a._b = 12; a._c = 3; a._d = 4; return 0; }
我们假设:位段分配的内存中的比特位是从右向左使用的,分配剩余的比特位不够使用时,浪费掉剩余内存。
则:
- 我们先定义位段,如下图:
- 执行程序:
a._a = 10;
10的二进制为1010,放入_a中,由于_a只有3bit,需要截断,所以舍弃最高位1,放入010:
- 执行程序:
a._b = 12;
,12的二进制为1100,刚好可以放入,如下图:
- 执行程序:
a._c = 3;
,3的二进制为11,由于_c有5bit,高位添0,放入00011,如下图:
- 执行程序:
a._d = 4;
,4的二进制为100,放入0100,如下图:
- 程序就基本执行完了,那么内存中是什么样的呢?根据上面分析,我们一开始给结构体初始化为0,我们可以得到:
由于机器是小端存储,所以内存上应该是:62 03 04.
经过调试,可以看到:
以上也证明了, 在VS2022上,位段分配的内存中的比特位是从右向左使用的,分配剩余的比特位不够使用时,浪费掉剩余内存,重新开辟新的空间。
当然,不同平台得到的结果也可能会不同,这正是位段的缺点,可移植性差,接下来我们看看位段的跨平台问题。
五,位段的跨平台问题
- int 位段被当成有符号数还是无符号数是不确定的。
- 位段中最大位的数目不能确定。(16位机器最大16,32位机器最大32,写成27,在16位机器会出问题)。
- 位段中的成员在内存中从左向右分配还是从右向左分配的标准尚未定义。
- 当一个结构包含两个位段,第二个位段成员比较大,无法容纳打一个位段剩余的位时,是舍弃剩余的位还是利用,这是不确定的。
总结:跟结构相比,位段可以达到同样的效果,并且可以很好的节省空间,但是有跨平台的问题存在。
六, 位段的应用
位段由于跨平台的问题,真正的用途的其中一个是计网的IP数据报:
😄 创作不易,你的点赞和关注都是对我莫大的鼓励,再次感谢您的观看😄