目录
一、位段的定义
二、位段的声明
三、位段的内存分配
四、位段在内存中的存储方式
五、位段的优点
六、位段的跨平台问题
七、位段的应用
八、位段使用的注意事项
一、位段的定义
信息的存取一般以字节为单位。实际上,有时存储一个信息不必用一个或多个字节。
例如:"真"或"假"可以用0或1表示,只需1位即可。这时我们就可以用位段来进行存储。
那么什么是位段呢?
位段(Bit Field)是C语言中的一种数据结构,它允许程序员在一个结构体中以位为单位来指定其成员所占的内存长度。这种以位为单位的成员称为"位段"或"位域"。
位段的定义要借助于结构体,即以二进制位为单位定义结构体成员所占存储空间,从而可以按"位"来访问结构体中的成员。
位段与结构体形式与用法上是很相近的,但位段可以用来描述更为细腻的数据级别。
二、位段的声明
位段的声明语法形式如下:
struct 标签
{
位段成员类型 位段成员名:分配内存的大小;
}
举例:
struct A
{
int _a : 2; //分配2bit的空间大小
int _b : 5;
int _c : 10;
int _d : 30;
};
//A就是一个位段类型
//int:位段的成员类型 _a: 位段成员名 2:分配内存的大小
注意:
- 位段的成员必须是(整型):int,unsigned int 或signed int,char
- 位段的成员名后边有一个冒号和一个数字
- 位段成员名中的 "_" 是可以是可以省略的,加上下划线与不加都可以,只是一种命名风格。
- 位段中分配内存的大小,宽度必须小于等于指定类型的位宽度。(即:冒号后面的数字的bit不能超过前面类型所占的bit)
- 位段的位指的是二进制位。
位段的声明应在结构体/联合体中。
原因:
位段是依赖结构体/联合体来实现的。在位段的声明和使用中,虽然可以决定用多少位来存储数据,但不能认为位段就是可以自定义的数据类型。可以理解为:位段是依赖于结构体实现的自定义类型。可以认为位段是将一个盒子里面的格子自定义大小。
三、位段的内存分配
位段所占内存大小为多少呢?
我们测试下面一段代码:
struct S
{
char _a : 3;
char _b : 4;
char _c : 5;
char _d : 4;
};
int main()
{
printf("%d\n", sizeof(struct S));
return 0;
}
测试结果如图所示:
为什么会是3个字节(byte)呢?
原因:
- 位段的空间上是按照需要以4个字节( int )或者1个字节( char )的方式来开辟的。
- 一个位段必须存储在同一存储单元(即字)之中,不能跨两个单元。如果其单元空间不够,则剩余空间不用,从下一个单元起存放该位段。
四、位段在内存中的存储方式
我们看如下一段代码:
#include <stdio.h>
struct S
{
char a : 3;
char b : 4;
char c : 5;
char d : 4;
};
int main()
{
struct S s = { 0 };
s.a = 10;
s.b = 12;
s.c = 3;
s.d = 4;
return 0;
}
调试结果如下:
我们调试发现:
10,12,3,4在内存中是以16进制存放,为什么是62 03 04呢?
分析如下:
可知:位段中的成员在内存中是从右向左分配。
注意:
- 大小端指的是如果一个数据存储时超过一个字节的时候,才有字节顺序。这里是一个字节(内部),所以不谈顺序。
- 这里是先开辟一个字节,再开辟一个字节,最后再开辟一个字节,所以存放顺序一定是如图所示的存放方式。
五、位段的优点
可以使数据单元节省储存空间,避免不必要的空间浪费。
但是所谓节省空间是在一定程度上节省空间,并不是完全不浪费。
六、位段的跨平台问题
1、int被位段作为是:无符号整数还是有符号整数,这个并没有做出明确的规定。
2、位段中最大位的数目不能确定。(16位机器最大16,32位机器最大32,写成27,在16位机器就会出现问题)
3、位段中的成员在内存中是从左向右分配,还是从右向左分配尚未定义。(vs中是从右向左)
4、当一个结构包含两个位段,假设第二个位段成员无法全部容纳于第一个剩余的位时,是把一个空间填满再放到新开辟的空间,还是直接全部放到新的空间,这个没有明确的规定。(vs中是直接全部放到新的空间)
补充:
- 只有在位段的时候,int是没有确定是使用unsigned还是signed。除此之外int都是signed int。
- 而char才是在使用和不使用位段的时候都是不确定的。
总结:位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使用位段。
七、位段的应用
1、在一些特定的应用场景中,需要对一个整数类型的变量中的每一位进行单独的控制或访问。例如,硬件寄存器常包含一些特定的位用于表示设备的状态,配置选项或标志位。使用位段可以让程序员更方便地访问和控制这些位,不需要进行位运算。
2、在网络协议中,IP数据报的格式。可以看到其中很多属性只需要几个bit位就能描述,使用位段就能够实现想要的效果,也节省了空间,这样网络传输的数据报大小也会较小一些,对网络的畅通是有帮助的。
八、位段使用的注意事项
1、位段无地址,不能对位段进行取地址运算。
原因:
位段的几个成员共用一个字节,这样有些成员的起始位置并不是某个字节的起始位置,那么这些位置处是没有地址的。
因为内存中是每个字节分配一个地址,一个字节内部的bit位是没有地址的。
所以不能对位段的成员使用&操作符,这样就不能使用scanf直接对位段的成员输入值,只能是先输入一个值存放在一个变量中,然后再赋值给位段的成员。
如下所示:
#include <stdio.h>
struct A
{
int _a : 2;
int _b : 5;
int _c : 10;
int _d : 30;
};
int main()
{
struct A sa = { 0 };
scanf("%d", &sa._b); //这是错误的
//正确的示范
int b = 0;
scanf("%d", &b);
sa._b = b;
return 0;
}
2、位段里的成员类型要尽量保持一致。否则会带来没必要麻烦,它内存开辟的时候可能会跟期望的不一样。
原因:
位段使用的场景本来就非常苛刻。如果再类型不一样,这样写出来的代码可控性就会变得差,而且它有许多不确定性,导致了它的不跨平台性。
3、位段在访问时与结构体访问方式相同,通过点操作(.)进行访问。访问时注意不要超出了所定义的位段大小。
4、两位段相邻时,相同数据类型的位段在编译过程中可以提高存储效率,而不同数据类型的位段则更可能需要考虑数据对齐而降低存储效率。