在进行嵌入式软件开发时,我们经常需要对数据的某位进行操作,通常会使用位操作符实现。虽然使用位操作符快捷,但是给人的感觉不够直观、优雅,本文介绍基于共用体和位域实现灵活数据位操作的方法。
共用体是一种特殊的数据类型,它可以将不同的数据存储在相同的内存上。我们可以定义一个带多个成员的共用体,但是任何时候只有最多一个成员的值是有效的,因此它更像是一种使数据看起来更加规范的操作。
下面就是常见的共用体定义:
#include "stdio.h"
typedef union
{
char a;
unsigned short int b;
float c;
double d;
} unionX;
int main(void)
{
unionX unionData = {0};
printf("sizeof(unionData) : %d\r\n", sizeof(unionData));
printf("sizeof(unionData.a) : %d\r\n", sizeof(unionData.a));
printf("sizeof(unionData.b) : %d\r\n", sizeof(unionData.b));
printf("sizeof(unionData.c) : %d\r\n", sizeof(unionData.c));
printf("sizeof(unionData.d) : %d\r\n", sizeof(unionData.d));
printf("unionData addr : 0x%X\r\n", &unionData);
printf("unionData.a addr : 0x%X\r\n", &unionData.a);
printf("unionData.b addr : 0x%X\r\n", &unionData.b);
printf("unionData.c addr : 0x%X\r\n", &unionData.c);
printf("unionData.d addr : 0x%X\r\n", &unionData.d);
return 0;
}
我们打印共用体大小、共用体成员大小、共用体地址、共用体成员地址,了解这些共用体在内存中的分布情况。打印结果如下:
可以用如下的图要来表示共用体在内存中的分布:
通过这张图可以很容易看出来,共用体的大小由共用体内最大的数据决定,同时每个成员的都共享这块和最大数据大小一致的内存空间。因此,每次对共用体中的成员赋值,都会影响到其它成员,也就是共用体成员中最多只有一个成员数据是有效的。
一般的结构体都是按照标准的数据类型对成员定义,位域允许按位对成员进行定义,我们可以指定成员占用的位数,进而实现对数据的位操作,形式上比使用位操作符更加简洁、优雅。
例如我们需要对一个unsigned char类型数据进行位操作,可以使用如下的位域操作:
#include "stdio.h"
typedef struct
{
unsigned char x : 1; // 对应于bit0
unsigned char y : 3; // 对应于bit1-3
unsigned char z : 4; // 对应于bit4-7
} bits_t;
int main(void)
{
bits_t bitStatus = {0};
bitStatus.x = 0;
bitStatus.y = 7;
bitStatus.z = 15;
printf("bitStatus.x : %d\r\n", bitStatus.x);
printf("bitStatus.y : %d\r\n", bitStatus.y);
printf("bitStatus.z : %d\r\n", bitStatus.z);
printf("bitStatus : 0x%02X\r\n", *(unsigned char *)&bitStatus);
printf("bitStatus size : 0x%02X\r\n", sizeof(bitStatus));
return 0;
}
我们将x、y、z分别设置为0、7、15,然后打印x、y、z、结构体的值、结构体大小。结果如下:
如果对位域成员的赋值超出了位占用能表示的最大大小,编译器会给出警告:
可以用如下的图要来表示位域成员对数据位的控制情况:
可以看出来,位域成员对位的占用是从低位开始的,根据位域成员:后的数据决定占用位数。
经过上面的介绍,我们已经很清楚共用体和位域的功能,其实可以把二者的优点结合起来,实现很简洁、优雅的位操作。
形式上可以写成以下两种形式:
(1)位域在共用体内定义
typedef union
{
struct bits
{
unsigned char x : 1; // 对于bit0
unsigned char y : 3; // 对于bit1-3
unsigned char z : 4; // 对于bit4-7
} bitStatus;
unsigned char value;
} unionX;
(2)位域在共用体外定义
typedef struct
{
unsigned char x : 1; // 对于bit0
unsigned char y : 3; // 对于bit1-3
unsigned char z : 4; // 对于bit4-7
} bits;
typedef union
{
bits bitStatus;
unsigned char value;
} unionX;
我们设置unionData.value的值为0xFE,然后打印各个成员的值、共用体的成员大小、共用体大小。测试语句如下:
int main(void)
{
int i;
unionX unionData = {0};
unionData.value = 0xFE;
printf("unionData.bits.x : 0x%x\r\n", unionData.bitStatus.x);
printf("unionData.bits.y : 0x%x\r\n", unionData.bitStatus.y);
printf("unionData.bits.z : 0x%x\r\n", unionData.bitStatus.z);
printf("sizeof(bitStatus) : 0x%x\r\n", sizeof(unionData.bitStatus));
printf("sizeof(value) : 0x%x\r\n", sizeof(unionData.value));
printf("sizeof(unionData) : 0x%x\r\n", sizeof(unionData));
return 0;
}
打印结果如下:
结合我们之前对共用体和位域的示意图,可以画出共用体和位域实现的数据位操作在内存上的分布如下:
其实,使用共用体和位域实现数据位操作好处就是不需要直接使用位域结构体对整体赋值,使操作变得更加直观、简洁、优雅。