10.5 位段
关于结构,我们最后还必须提到它们实现位段(bit field)的能力。位段的声明和结构类似,但是它的成员是一个或多个位的字段。这些不同长度的字段实际上存储于一个或多个整型变量中。
位段的声明和任意普通的结构成员声明相同。但有两个例外。首先,位段成员必须声明为int、signed int或unsigned int类型。其次,在成员名的后面是一个冒号和一个整数,这个整数指定该位段所占用的位的数目。
提示:
用signed或unsigned整数显式地声明位段是个好主意。如果把位段声明为int类型,它究竟被解释为有符号数还是无符号数则是由编译器决定的。
提示:
注重可移植性应该避免使用位段。由于下面这些与实现有关的依赖性,位段在不同的系统中可能有不同的结果。
1.int位段被当做有符号数还是无符号数。
2.位段中位的最大数目。许多编译器把位段成员的长度限制在一个整型值的长度之内,所以一个能够运行于32位整数的机器上的位段声明可能在16位整数的机器上无法运行。
3.位段中的成员在内存中是从左往右分配的还是从右往左分配的。
4.当一个声明指定了两个位段,且第2个位段比较大,无法容纳于第1个位段剩余的位时,编译器有可能把第2个位段放在内存的下一个字,也可能直接放在第1个位段后面,从而在两个内存位置的边界上形成重叠。
下面是一个位段声明的例子:
struct CHAR {
unsigned ch : 7;
unsigned font : 6;
unsigned size : 19;
};
struct CHAR ch1;
这个声明取自一个文本格式化程序,它可以处理多达128个不同的字符值(需要7位)、64种不同的字体(需要6位)以及0~524287个单位的长度。这个size位段过于庞大,无法容纳于一个短整型,但其余的位段都比一个字符还短。位段是程序员能够利用存储ch和font所剩余的位来增加size的位数,这样就避免了声明一个32位的整数来存储size字段。
许多16位整数机器的编译器会把这个声明标志为非法,因为最后一个位段的长度超过了整型长度。但在32位的机器上,这个声明将根据下面两种可能的方法创建ch1。
这个例子说明了一个使用位段的好理由:它能够把长度为奇数的数据包装在一起,节省存储空间。当程序需要使用成千上万的这类结构时,这种节省方法就会变得相当重要。
另一个使用位段的理由是它们可以很方便地访问一个整型值的部分内容。让我们研究一个例子,它可能出现于操作系统中。用于操作软盘的代码必须与磁盘控制器通信。这些设备控制器常常包含了几个寄存器,每个寄存器又包含了许多包装在一个整型值内的不同的值。
位段就是一种访问这些单一值的方便方法。假定磁盘控制器其中的一个寄存器是如下定义的:前5个位段每个都占1位,其余几个位段则更长一些。在一个从右向左分配位段的机器上,下面这个声明允许程序员方便地对这个寄存器的不同位段进行访问:
struct DISK_REGISTER_FORMAT {
unsigned command :5;
unsigned sector :5;
unsigned track :9:
unsigned error_code :8:
unsigned head_loaded :1;
unsigned write_protect :1;
unsigned disk_spinning :1;
unsigned error_occurred :1;
unsigned ready :1;
};
假如磁盘寄存器是在内存地址0xc0200142进行访问的,我们可以声明下面的指针常量:
#define DISK_REGISTER ( ( struct DISK_REGISTER_FORMAT * )0xc0200142 )
做了这个准备工作后,实际需要访问磁盘寄存器的代码就变得简单多了,如下面的代码段所示:
/*
**告诉控制器是从哪个扇区哪个磁道开始读取。
*/
DISK_REGISTER->sector = new_sector;
DISK_REGISTER->track = new_track;
DISK_REGISTER->command = READ;
/*
**等待,直到操作完成(ready变量变成真)。
*/
while( !DISK_REGISTER->ready ){
;
}
/*
**检查错误。
*/
if( DISK_REGISTER->error_occurred ){
switch( DISK_REGISTER->error_code ){
...
}
}
使用位段只是基于方便的目的。任何可以用位段实现的任务都可以使用移位和屏蔽来实现。例如,下面代码段的功能和前一个例子中第1个赋值的功能完全一样:
#define DISK_REGISTER (unsigned int *)0xc0200142
*DISK_REGISTER &= 0xfffffc1f;
*DISK_REGISTER |= ( new_sector & 0x1f ) << 5;
第1条赋值语句使用位AND操作把sector字段清零,但不影响其他的位段。第2条赋值语句用于接受new_sector的值,AND操作可以确保这个值不会超过宽度。接着,把它左移到合适的位置。然后使用位OR操作把这个字段设置为需要的值。
提示:
在源代码中,用位段表示这个处理过程更为简单一些,但在目标代码中,这两种方法并不存在任何区别。无论是否使用位段,相同的移位和屏蔽操作都是必需的。位段提供的唯一优点是简化了源代码。这个优点必须与“位段的移植性较弱”这个缺点进行权衡。
C和指针 第10章 结构和联合 10.5 位段
于 2022-06-25 07:15:24 首次发布