操作位的方法除了位运算符之外,第2种方法是位字段。位字段是一个 signed int 或 unsigned int 类型变量中的一组相邻的位。C99 和 C11 新增了 _Bool 类型的位字段。
位字段通过一个结构声明来建立,该结构声明为每个字段提供标签,并确定该字段的宽度。
struct {
unsigned int autfd : 1;
unsigned int bldfc : 1;
unsigned int undln : 1;
unsigned int itals : 1;
} prnt;
根据该声明,prnt 包含4个1位的字段。现在,可以通过普通的结构成员运算符(.)单独给这些字段赋值:
prnt.itals = 0;
prnt.undln = 1;
由于每个字段恰好为1位,所以只能为其赋值1或0。变量 prnt 被储存在一个 int 大小的内存单元中,但是在本例中只使用了其中的4位。
带有位字段的结构提供一种记录设置的方便途径。许多设置就是简单的二选一。例如,开或关、真或假、字体的粗体或斜。如果只需要使用 1 位,就不需要使用整个变量。内含位字段的结构允许在一个存储单元中储存多个设置。有时,某些设置也有多个选择,因此需要多位来表示。这没问题,字段不限制 1 位大小。
struct {
unsigned int code1 : 2;
unsigned int code2 : 2;
unsigned int code3 : 8;
} prcode;
以上代码创建了两个2位的字段和一个8位的字段。可以这样赋值:
prcode.code1 = 0;
prcode.code2 = 3;
prcode.code3 = 102;
但是,要确保所赋的值不超出字段可容纳的范围。
如果声明的总位数超过了一个 unsigned int 类型的大小会怎样?
会用到下一个 unsigned int 类型的存储位置。一个字段不允许跨越两个 unsigned int 之间的边界。编译器会自动移动跨界的字段,保持 unsigned int 的边界对齐。一旦发生这种情况,第1个 unsigned int 中会留下一个未命名的“洞”。 可以用未命名的字段宽度“填充”未命名的“洞”。使用一个宽度为 0 的未命名字段迫使下一个字段与下一个整数对齐。
struct {
unsigned int field1 : 1 ;
unsigned int : 2 ;
unsigned int field2 : 1 ;
unsigned int : 0 ;
unsigned int field3 : 1 ;
} stuff;
这里,在 stuff.field1 和 stuff.field2 之间,有一个 2 位的空隙;stuff.field3 将储存在下一个unsigned int中。
字段储存在一个 int 中的顺序取决于机器。在有些机器上,存储的顺序是从左往右,而在另一些机器上,是从右往左。另外,不同的机器中两个字段边界的位置也有区别。由于这些原因,位字段通常都不容易移植。尽管如此,有些情况却要用到这种不可移植的特性。例如,以特定硬件设备所用的形式储存数据。