目录
1.什么是位段
段位的声明和结构体是类似的,但有两个不同之处:
1. 位段的成员必须是 int ,unsigned int,或 signed int,char类型 (整形家族),在C99中位段也可以是其他类型
2. 位段的成员名后有一个冒号和一个数字
例:
struct A
{
int _a : 2; //_a只能使用2个比特位大小
int _b : 5;
int _c : 10;
int _d : 30;
};
这里A就是位段的类型,冒号后面的数字表示该元素所占用空间的大小(单位:bite)
冒号后面数字的大小不能超过该成员所开辟空间的大小
这段A占多大空间呢
2.位段的计算
我们知道了位段,那么位段应该怎么计算呢?
开辟规则:位段的空间上是按照需要以4个字节( int )或者1个字节( char )的⽅式来开辟的。
例:
struct B
{
char _a : 3;
char _b : 4;
char _c : 5;
char _d : 4;
};
因为是char类型,所以按照需要一次开辟1个字节 即:8个bit位,先将数据往里放,不够用了在继续开辟
第一次_a存入3个bit,然后_b继续存入4个bit,此时我们开辟的空间已经剩下1bit,支持不了_c的存放,于是又开辟了8个bit,这时分两种情况:
- 使用前面剩下的1bit空间,然后继续使用新开辟的8bit中的4bit
- 丢弃前面开辟的1bit空间,直接使用新开辟的空间存放_c
这两种情况并没有明确的规定使用哪一个,根据编译器的不同可能情况各异。我当前使用的是VS2022,是按照第二种情况开辟空间的,此时_c成员使用了5bit,还剩下3bit,已经不足以存放_c了,于是又开辟了一个空间存放_d。到此所有的成员已存放完成,使用了3字节,浪费了8bit的空间
3. 位段的内存分配
位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使⽤位段。
位段使用因环境而异,不同的环境可能需要相符合的位段,虽然麻烦,但是位段很节省空间
例:
struct B
{
char _a : 3;
char _b : 4;
char _c : 5;
char _d : 4;
};
int main()
{
struct B a = { 0 };
a._a = 10;
a._b = 12;
a._c = 3;
a._d = 4;
return 0;
}
我们知道结构体B的大小是3字节,现在我们想在这些空间上面赋一些值
在内存分配的时候我们可能会遇到一个问题,他是怎么分配资源的呢?从左向右还是从右向左呢?
这是一条C语言标准尚未定义的规则,所以不同编译器下可能不一样,在VS2022编译器下经过测试是按照从右往左使用的
所以VS2022上的分配情况:
存放数据示图:
原本_a是1010,由于_a只有3个bit的空间,所以只能舍弃前面的1
我们将二进制转换为十六进制,
先写出这三个空间的二进制:01100010 00000011 00000100
转换十六进制:64 03 04
4.位段的跨平台问题
- int位段被当成有符号数还是⽆符号数是不确定的。
- 位段中最⼤位的数⽬不能确定。(16位机器最⼤16,32位机器最⼤32,写成27,在16位机器会 出问题。
- 位段中的成员在内存中从左向右分配,还是从右向左分配,标准尚未定义。
- 当⼀个结构包含两个位段,第⼆个位段成员⽐较⼤,⽆法容纳于第⼀个位段剩余的位时,是舍弃剩余的位还是利⽤,这是不确定的。
在使用位段时需要十分谨慎,因为跟结构相⽐,位段可以达到同样的效果,并且可以很好的节省空间,但是有跨平台的问题存在。
5.位段的应⽤
使用微信,qq,以及其他的一些聊天软件时,我们只需将发送的内容写入对话框并点击发送即可,但是这其中要实现不像我们操作的这么简单
比你想象得更加复杂的多,当你发消息给某人时,系统需要知道这个信息从哪来,到哪去,以及这条信息的生存时间等等
如图:
如上图我们使用段位就可以节省大量空间,而且当我们发送消息时,发出去得数据包越小,我们传输的效率就越高,打个比方,在满是汽车的高速公路上,如果所有车都是小汽车,那么交通就会很便利,流畅,但如果全是大货车,体型很大的车,那么就会让交通很堵塞,交通不便利
6.位段使⽤的注意事项
位段的⼏个成员共有同⼀个字节,这样有些成员的起始位置并不是某个字节的起始位置,那么这些位 置处是没有地址的。内存中每个字节分配⼀个地址,⼀个字节内部的bit位是没有地址的。所以不能对位段的成员使⽤&操作符,这样就不能使⽤scanf直接给位段的成员输⼊值,只能是先输⼊ 放在⼀个变量中,然后赋值给位段的成员。
struct B
{
char _a : 3;
char _b : 4;
char _c : 5;
char _d : 4;
};
int main()
{
struct B a = { 0 };
scanf("%d", &a._a);//错误的演示,编译器会报错,不允许使用位域的地址
//正确的操作
int b = 10;
a._a = b;
return 0;
}