在计算机用于过程控制、参数检测或数据通信领域时,控制信息往往只占一个字节中的一个或几个二进制位,常常在一个字节中放几个信息.那么,如何向一个字节中的一个或几个二进制位赋值和改变它的值呢?通常有两种方法:
(1)通过位运算中的移位操作来实现对一个字节中某几个二进制位进行控制.但这种方法非常麻烦,容易出错.C语言中常见的位操作运算符有:
1.异或运算符(^):参加运算的两个二进制位相同,则为0,不同则为1.
2.按位或运算(|) :参加运算的两个二进制位只要有一个为1,则结果为1,否则为0.
(2)通过C语言的"位域"来实现对一个字节中某几个二进制位进行控制.
一、位域的概念
C语言允许在一个结构体中以位为单位来指定其成员所占内存长度,这种以位为单位的成员称作"位段"或"位域"(bit field).利用位域能够用较少的位数来存储数据.例如:
struct packed_data
{
unsigned a:2;
unsigned b:3;
unsigned c:4;
int i;
};
struct packed_data data;
可以看出,a、b、c共占9bit,占1个字节多,不到两个字节.而变量i为int型,占2个字节.因此在内存中,a、b、c连续存放,且之后的7bit空间闲置不用,变量i从另一字节起存放.注意,在存储单元中位段的空间分配因机器而异.在计算机使用的C系统中,一般是从右向左分配的.
二、位域的注意事项
(1)位域成员的引用
对位域中的数据引用,可采用下面的方法.例如:
data.a=2;
data.b=7;
data.c=9;
注意位域允许的最大值范围.因为data.a只占2bit,最大值为3.因此自动取赋予它的数的低两位.如果写成:
data.a=8
则由于8的二进制数为1000,而data.a只有2bit,取1000的低2bit,故data.a=0.
(2)位域成员的类型必须指定为unsigned或int型.
(3)若某一位域要从另一字节开始存放,则可以采用:
unsigned a:1;
unsigned b:2;
unsigned :0;
unsigned c:3;
本来a、b、c应该在同一个存储单元中,由于使用了长度为0的位域,其作用是使下一个位域从下一个存储单元开始存放.因此,a、b存储在一个存储单元中(剩余的位处于闲置状态),c在下一个存储单元中.
(4)一个位域必须存储在同一存储单元中,不能跨两个单元.如果第一个存储单元空间不能容纳下一个位域,则该空间不用,而从下一个单元起存放该位域.例如:
unsigned a:2;
unsigned b:4;
unsigned c:6;
根据此原则,a和b在同一个存储单元中(剩余的位处于闲置状态),c则在另一个存储单元中.
(5)位域的长度不能大于存储单元的长度,也不能定义位域数组.例如:
unsigned char a:9;
这种写法就是错误的.
(6)位域可以在数值表达式中引用,它会被系统自动的转换成整型数.例如:
data.a+5/data.b
三、位域的使用
typedef unsigned char BYTE;
typedef union {
BYTE bytes[15];
struct {
BYTE rs:1;
BYTE freq:7;
BYTE dbm:2;
BYTE fosc:3;
BYTE baudrate:1;
BYTE mode:1;
BYTE en2:1;
BYTE crcen:1;
BYTE crclen:1;
BYTE addrlen:6;
BYTE addr1[5];
BYTE addr2[5];
BYTE len1:8;
BYTE len2:8;
} bf;
} NRFCMD, *PNRFCMD;
这段代码是从NRF2401字符驱动中挑选出来的.每次收发数据都是15Bytes,共120bit,其中每一bit或每几个bit所表示的含义不同.通过位域可以非常方便的操作某几个bit.由于结构体bf和字符数组bytes[15]是联合体的两个成员,它们共享内存地址空间,因此对结构体bf中成员的操作也就是对字符数组bytes[15]中某些位的操作.例如:
PNRFCMD cmd = &device.cmd;
memset(&device, 0, sizeof(device));
cmd->bf.rs = 1; // default recv
cmd->bf.freq = 2; // default 2400 + 2 MHz
cmd->bf.dbm = 3; // default 0dBm
cmd->bf.fosc = 3; // default 16MHz
cmd->bf.baudrate = 0; // default 250kbps
cmd->bf.mode = 1; // default Shock Burst
cmd->bf.en2 = 0; // default disable CH2
cmd->bf.crcen = 1; // default enable CRC
cmd->bf.crclen = 1; // default CRC-16
cmd->bf.addrlen = 40; // default 40bit address