什么是位段?
位段的声明和结构体是类似的,但有两个不同之处:
- 位段的成员必须是int、unsigned int、signed int或char(属于整型家族)类型。
- 位段的成员名后面有一个冒号和一个数字。
举个例子:
struct A
{
int a : 2;//a只需要2个比特位
int b : 5;//b只需要5个比特位
int c : 10;//c只需要10个比特位
int d : 30;//d只需要30个比特位
};
这里的A就是一个位段类型。
注:冒号后面的数字代表该成员需要用的bit位的位数,所以该数字不能超过该成员变量的变量大小,单位为bit。
int e : 33;//error
位段大小的计算
知道了什么是位段,那么位段的大小是如何计算的呢?
规则:位段的空间是按照需要,以4个字节(int)或1个字节(char)的方式来开辟的。
举个例子:
struct S
{
char a : 3;
char b : 4;
char c : 5;
char d : 4;
};
首先,该位段类型的成员类型是char类型,我们一次开辟1个字节(即8个bit位)的内存大小(不够再继续开辟)。
第一次开辟的8个bit位中,成员a用去了3个bit位,成员b用去了4个bit位,此时剩下1个bit位,而成员c需要5个bit位,于是又新开辟8个bit位,到这里便有两种情况:
- 情况一:成员c先把上次开辟的8个bit位中剩下的一个bit位用去,再在新开辟的8个bit位中用去4个bit位。
- 情况二:成员c之间使用新开辟的8个bit位中的5个bit位,而上次开辟的8个bit位中剩下的那个bit位被浪费。
关于这一点,C语言标准并没有定义,于是不同编译器就可能按照不同的方式来计算。我当前使用的VS2013是按照第二种情况进行计算的,所以此时成员c用去了新开辟的8个bit位中的5个bit位,剩下3个bit位,不够成员d使用,所以又新开辟8个bit位供成员d使用,一个开辟了3次,即24个bit位,即3个字节,所以该位段类型的大小在VS2013编译器下的大小为3个字节。
位段的内存分配
#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;
}
上面我们已经计算了该位段类型在VS2013编译器下的大小为3个字节,那么当我创建一个该位段类型的变量并对齐赋上一些值时,这些值在内存中是如何分配的呢?
在内存分配的过程中又可能遇到一个问题:当开辟好8个bit位,我们使用这8个bit位的时候,是从左向右使用还是从右向左使用的呢?
这又是一个C语言标准尚未定义的规则,所以不同编译器下又可能不一样,在VS2013编译器下经测试是按照从右向左使用的。
于是在VS2013编译器下是这样分配的:
当把赋的值分配进去时就是这样的:
因为在赋值之前已经将创建的位段类型的s变量赋初值为0了,所以白色区域(即浪费了的区域)代表的数字就是0。
于是把赋的值分配进去后,这3个字节的内容用二进制表示就是:
01100010 00000011 00000100
将其化为十六进制就是:62 03 04
位段的跨平台问题
在上面我们已经看到了,关于位段的很多问题都是C语言标准未定义的,所以在不同编译器中的情况就会不一样,造成位段跨平台问题的主要原因有以下几点:
- int位段被当成有符号数还是无符号数是不确定的。
- 位段中最大位的数目不能确定。(16位机器最大为16,32位机器最大为32,写成27在16位平台就会出问题)
- 位段中的成员在内存中是从左向右分配,还是从右向左分配尚未定义。
- 当一个结构包含两个位段,第二个成员位段比较大,无法容纳于第一个位段剩余的位时,是舍弃剩余的位还是利用,这是不确定的。
所以在考虑是否使用位段时要十分小心,因为跟普通的结构体相比,位段虽然可以达到同样的效果,并且可以很好地节省空间,但是存在跨平台的问题。
位段的应用
当我们用微信或是QQ向好友发送消息时,只需将要发送的内容写入对话框并点击发送即可,但你以为这其中的过程真的就那么简单吗?
其实比你想象的要复制得多,当你要发送一条消息给某人时,系统必须知道这条消息从哪里来,要到那里去以及这条消息的生存时间等等,如下图:
这时,我们运用位段就能节省大量空间,而且当我们发消息时,因为发出去的数据包越小,我们的信息传输效率就会越高,这好比在高速公路上如果全是小汽车(位段类型将空间运用到极致),那么交通会很流畅,而如果全是大卡车(普通结构体类型占用空间较大),那么就会造成交通堵塞。