文章转自:https://www.cnblogs.com/programmer-home/p/13207403.html 这个文章作者的其它c/c++/linux文章也值得看一看。
一、引入
结构体再熟悉不过了,一般我们定义一个结构体会这么写
struct tagStudent { unsigned int Id; unsigned int age; char sex[12]; char name[50]; }
这便是一个完整的结构体定义,其内部各成员有着严格的对齐机制(关于结构体对齐机制,网上文章很多)。
了解C语言结构体对齐机制的人都知道,结构体内部存在一定的内存空间浪费。并且在有些场景下,数据存储时并不需要占用一个完整的字节,而只需占几个或一个二进制位(即bit位)即可。例如在存放一个开关量时,只有0和1两种状态,用一位二进位即可。再例如上述结构体定义中,在32位机器下,unsigned int 类型的成员age占据了4个字节,因此年龄age的取值范围为0 ~ 4294967295,而现实中一个人的年龄往往0 ~ 150之间是绝对够用了,这就造成了内存空间浪费。有的人或许会说可以将age定义为 unsigned short,32位机器unsigned short取值范围是0 ~ 65536,依旧有很多无效内存使用不上。
针对以上情况,为了节省存储空间并使处理简便,C语言又提供了一种数据结构,称为“位域”或“位段”。所谓“位域”是把一个字节中的二进位划分为几个不同的区域,并说明每个区域的位数。每个域有一个域名,允许在程序中按域名进行操作。这样就可以把几个不同的对象用一个字节的二进制位域来表示。
之所以提出位域,就是由于某些信息的存储只需要几个bit位就可以,而不需要一个完整的字节(8个bit位),同时也是为了节省存储空间和方便处理。
二、位域的表示方法
位域的表示方法为:
struct 位域结构体名 {
类型 位域成员名: 位域长度
}
例如:
struct tagStudent { unsigned int Id: 2; unsigned int age: 2; char sex[12]: 5; char name[50]: 7; }
该结构体整个占用2+2+5+6=15个bit位,共15%8+1=2个字节。
三、位域的使用
位域的使用和结构成员的使用相同,其一般形式为:位域变量名·位域名。位域允许用各种格式输出。
#include "stdio.h" int main() { struct tagStudent { unsigned int Id: 1; unsigned int age: 3; unsigned int cla: 5; } Student, *pStudent; Student.Id = 1; Student.age = 7; Student.cla = 15; printf("%d %d %d\n", Student.Id, Student.age, Student.cla); pStudent = &Student; pStudent->Id = 0; pStudent->age &= 3; pStudent->cla |= 15; printf("%d %d %d\n", pStudent->Id, pStudent->age, pStudent->cla); return 0; }
上述结构体成员中,Id占1个比特位,取值范围0、1;age占3个比特位,取值范围0~8;cla占5个比特位,取值范围0~32。该结构体总共占9个比特位,2个字节。
四、注意事项
1、一个位域必须存储在同一个字节中,不能跨两个字节。
若一个字节所剩空间不够存放另一位域时,应从下一单元起存放该位域。例如:
struct bs { unsigned a: 4 unsigned b: 3 unsigned c: 4 /* 从下一字节开始存放 */ }
在这个位域定义中,a占第一字节的4位,b占第一字节的3位,此时第一个字节还剩1位。但接下来的c需要占4位,显然第一个字节不够了,这时c从第二字节开始占4个字节,注意不存在c先把第一个字节剩下的1位占了,再从第二个字节占3位。
2、由于位域不允许跨两个字节,因此位域的长度不能大于一个字节的长度,也就是说不能超过8位二进位。例如下面的定义非法:
struct bs { unsigned a: 10 /* 10大于一个字节长度,非法 */ unsigned b: 3 }
3、位域可以无位域名,这时它只用来作填充或调整位置。无名的位域是不能使用的。
struct bs { int a: 1 int : 3 /* 这3个二进制位不能使用 */ int b: 3 int c: 2 };
4、如果位域的位域长度为0表示是个空域,同时下一个域应当从下一个字节单元开始存放。
struct bs { unsigned a: 4 unsigned : 0 /* 空域 */ unsigned b: 4 /* 从下一单元开始存放 */ unsigned c: 4 }
5、位域的本质上就是一种结构体类型,不同的是其成员是按二进制位来分配的。