6.9 位字段
在存储空间很宝贵的情况下,有可能需要将多个对象保存在一个机器字中
一种常用的方法是,使用类似于编译器符号表的单个二进制位标志集合
外部强加的数据格式(如硬件设备接口)也经常需要从字的部分值中读取数据
考虑编译器中符号表操作的有关细节
程序中的每个标识符都有与之相关的特定信息
例如,它是否为关键字,它是否是外部的且(或)是静态的,等等
对这些信息进行编码的最简洁的方法就是使用一个 char
或 int
对象中的位标志集合
通常采用的方法是,定义一个与相关位的位置对应的 “ 屏蔽码 ” 集合:
#define KEYWORD 01
#define EXTRENAL 02
#define STATIC 04
或
enum { KEYWORD = 01, EXTERNAL = 02, STATIC = 04 };
这些数字必须是 2 的幂
这样,访问这些位就变成了用第 2 章中描述的移位运算、屏蔽运算及补码运算进行简单的位操作
语句 flags |= EXTERNAL | STATIC;
在程序中经常出现
该语句将 flags
中的 EXTERNAL
和 STATIC
位置为 1
而语句 flags &= ~(EXTERNAL | STATIC);
则将它们置为 0
并且,当这两位都为 0
时,表达式 if ((flags & (EXTERNAL | STATIC)) == 0) ...
的值为真
尽管这些方法很容易掌握,但是,C 语言仍然提供了另一种可替代的方法
即直接定义和访问一个字中的位字段的能力,而不需要通过按位逻辑运算符
位字段(bit-field),或简称字段,是 “ 字 ” 中相邻位的集合
字(word)是单个的存储单元,它同具体的实现有关
例如,上述符号表的多个 #define
语句可用下列 3 个字段的定义来代替:
struct {
unsigned int is_keyword : 1;
unsigned int is_extern : 1;
unsigned int is_static : 1;
} flags;
这里定义了一个变量 flags
,它包含 3 个一位的字段
冒号后的数字表示字段的宽度(用二进制位数表示)
字段被声明为 unsigned int
类型,以保证它们是无符号量
单个字段的引用方式与其它结构成员相同,如 flags.is_keyword
、flags.is_extern
等
字段的作用与小整数相似
同其它整数一样,字段可出现在算术表达式中
因此,语句 flags.is_extern = flags.is_static = 1;
将 is_extern
和 is_static
位置为 1
语句 flags.is_extern = flags.is_static = 0;
将 is_extern
和 is_static
位置为 0
下列语句:
if (flags.is_extern == 0 && flags.is_static == 0)
...
用于对 is_extern
和 is_static
位进行测试
字段的所有属性几乎都同具体的实现有关
字段是否能覆盖字边界由具体的实现定义
字段可以不命名,无名字段(只有一个冒号和宽度)起填充作用
特殊宽度 0
可以用来强制在下一个字边界上对齐
某些机器上字段的分配是从字的左端至右端进行的,而某些机器上则相反
这意味着,尽管字段对维护内部定义的数据结构很有用,但在选择外部定义数据的情况下,必须仔细考虑哪端优先的问题
依赖于这些因素的程序是不可移植的
字段也可以仅仅声明为 int
,为了方便移植,需要显式声明该 int
类型是 signed
还是 unsigned
类型
字段不是数组,并且没有地址,因此对它们不能使用 &
运算符