c语言编程时,常用的最小单元是一个字节,而有时信息并不需要占用一个完整的字节,只需一个或几个 bit 即可。例如在存放一个开关量时,只有 0 和 1 两种状态, 用一个 bit 即可。位域就是为了方便解决这样的需求的,它方便我们进行 bit 级别的操作,也可以节约存储空间。
位域的定义方法
--
位域的定义和结构体的定义非常相似,形式如下
struct 位域结构名
{
位域列表
...
};
例如,
struct BS
{
char a:2;
char b:6;
char c:1,
d:5, // 注意,结尾是逗号
:0; // 该字节定义结束时,用分号
};
以上定义了两个字节的位域BS
。第一个字节a
占 2 个 bit,b
占 6 个 bit。第二个字节c
占 1 个 bit,d
占 5 个 bit,该字节余下的 bit 用 0 填充。如果不想重复写变量的类型名,后面要用,
而不是 ;
,每个字节的定义结束位置都以 ;
结尾。
位域的使用
这里以 c 语言实例来说明位域的使用和注意事项,demo 代码如下
// 文件名 t.c
#include <stdio.h>
typedef struct __BS
{
char a:4;
char :0;
char b:4,
c:4;
}BS;
typedef union un
{
int i;
BS bs;
}UN;
int main()
{
UN un = {0};
BS* pBs = &(un.bs);
pBs->a = 1;
pBs->b = 2;
pBs->c = 3;
/* 以上 4 行与下面 3 行效果相同
un.bs.a = 1;
un.bs.b = 2;
un.bs.c = 3;
*/
printf("sizeof(BS): %ld\n", sizeof(BS));
printf("un.bs.a: %d, x.b: %d, x.c: %d\n", un.bs.a, un.bs.b, un.bs.c);
printf("un.i = %d\n", un.i);
return 0;
}
编译,执行之,得到结果如下:
$ gcc -o t t.c
$ ./t
sizeof(BS): 2
un.bs.a: 1, x.b: 2, x.c: 3
un.i = 12801
通过代码,我们知道位域也是可以使用指针的。联合体中的 i 是 int 型的,打印出它的值,可以表示出位域中的值,例如上面打印出 12801
,以 2 进制输出即为:
0011 0010 0000 0001
恰巧分别对应位域的 3 2 0 1。
输出顺序与运行机器是大端还是小端有关。
现在,我们队位域的定义做如下修改:
typedef struct __BS
{
char a:4;
char :0;
char b:4,
c:5; // 修改为 5
}BS;
这样,第二个字节就存放不下 b 和 c 了。我们编译执行,看看会输出什么
$ vim t.c
$ ./t
sizeof(BS): 3
un.bs.a: 1, x.b: 2, x.c: 3
un.i = 197121
发现,位域变为 3 字节了,197121
的二进制表示为
0011 0000 0010 0000 0001
很清楚了,当字节不足以容纳位域时,会自动从下一字节开始。
一个位域必须存储在同一个字节中,不能跨两个字节。如一个字节所剩空间不够存放另一位域时,应从下一单元起存放该位域。也可以通过无名域名使某位域从下一单元开始。