C语言位字段

位字段(bit-field)是一个由具有特定数量的位组成的整数变量。结构或联合的成员也可以是位字段。如果连续声明多个小的位字段,编译器会将它们合并成一个机器字(word)。这使得小单元信息具有更加紧凑的存储方式。当然,也可以使用位运算符来独立处理特定位,但是位字段允许我们利用名称来处理位,类似于结构或联合的成员。

位字段的声明格式为:

类型[成员名称]:宽度;


各部分的详细描述如下:

(1) 类型
指定一个整数类型,用来决定该位字段值被解释的方式。类型可以是 _Bool、int、signed int、unsigned int,或者为所选实现版本所提供的类型。这里的类型也可以包含类型限定符。

具有 signed int 类型的位字段会被解释成有符号数;具有 unsigned int 类型的位字段会被解释成无符号数。具有 int 类型的位字段可以是有符号或无符号的类型,由编译器决定。

(2) 成员名称
成员名称是可选的(可以不写)。但是,如果声明了一个无名称的位字段,就没有办法获取它。没有名称的位字段只能用于填充(padding),以帮助后续的位字段在机器字中对齐到特定的地址边界。

(3) 宽度
位字段中位的数量。宽度必须是一个常量整数表达式,其值是非负的,并且必须小于或等于指定类型的位宽。无名称位字段的宽度可以是 0。在这种情况下,下一个声明的位字段就会从新的可寻址内存单元开始。

当在一个结构或联合内声明一个位字段的时候,编译器会分配一个足以容纳它的可寻址内存单元。通常情况下,被分配的内存单元是一个 int 类型的机器字。

如果紧接着的位字段适合同一内存单元中剩下的空间,那么就被定义到与前面的位字段紧邻的位置。如果不适合的话,那么编译器就分配另外的内存单元,并在新单元的起始放置下一个位字段,或者跨过前一个内存单元的结尾和下一个内存单元的开头。

下面的例子重新定义了结构类型 struct Date,让其成员 month 和 day 只占据各自需要的位数。为了展示 _Bool 类型的位字段,我们为夏令时设定一个标签。这段代码以目标机器使用至少 32 位字为前提:
struct Date {
unsigned int month : 4;         // 1是1月;12是12月
unsigned int day   : 5;         // 月份中的日(1~31)
signed int   year  : 22;        // (-2097152~+2097151)
_Bool        isDST : 1;     // 如果是夏令时,则为true
};

n 位的位字段可以有 2n 个不同的值。结构成员 month 的取值范围是 0~15;成员 day 的取值范围是 0~31;成员 year 的值范围是 -2097152~+2097151。我们可以使用常见的初始化列表方式初始化一个 struct Date 类型的对象:
struct Date birthday = { 5, 17, 1982 };

对象 birthday 占据的存储空间大小与一个 32 位的 int 整数对象一样。和结构中其他成员所不同的是,位字段通常不会占据可寻址的内存位置,因此无法对位字段采用地址运算符(&)或宏 offsetof。

但是在其他方面,可以将位字段看作结构或联合成员,使用点和箭头运算符来获取,并以类似对待 int 或 unsigned int 变量的方式对其进行算术运算。因此,使用位字段重新定义的 Date 结构在函数 dateAsString()中不需作任何修改:
const char *dateAsString( struct Date d )
{
  static char strDate[12];
  sprintf( strDate, "%02d/%02d/%04d", d.month, d.day, d.year );
  return strDate;
}

下面的语句为对象 birthday 调用函数 dateAsString(),并采用标准函数 puts()输出结果:
puts( dateAsString( birthday ));