19-位字段
15.4 位字段
操控位的第2种方法是位字段(bit field)。位字段是一个 signed int
或 unsigned int
类型变量中的一组相邻的位(C99和C11新增了 _Bool
类型的位字段)。位字段通过一个结构声明来建立,该结构声明为每个字段提供标签,并确定该字段的宽度。例如,下面的声明建立了一个4个1位的字段:
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
中的顺序取决于机器。在有些机器上,存储的顺序是从左往右,而在另一些机器上,是从右往左。另外,不同的机器中两个字段边界的位置也有区别。由于这些原因,位字段通常都不容易移植。尽管如此,有些情况却要用到这种不可移植的特性。例如,以特定硬件设备所用的形式存储数据。