【C语言笔记】位域

一、位域的概念

有些数据在存储时并不需要占用一个完整的字节,只需要占用一个或几个二进制位即可。例如开关只有通电和断电两种状态,用 0 和 1 表示足以,也就是用一个二进位。正是基于这种考虑,C语言又提供了一种数据结构,叫做位域位段

位域是操控位的一种方法(操控位的另一种方法是使用按位运算符,按位运算符将在之后的笔记中做介绍)。

位域通过一个结构声明来建立:该结构声明为每个字段提供标签,并确定该字段的宽度。例如,下面的声明建立了个4个1位的字段:

struct 
{
   unsigned int autfd:1;
   unsigned int bldfc:1;
   unsigned int undin:1;
   unsigned int itals:1;
}prnt;

根据该声明, prnt包含4个1位的字段。现在,可以通过普通的结构成员运算符.单独给这些字段赋值:

prnt.itals = 0:
prnt.undin = 1;

由于每个字段恰好为1位,所以只能为其赋值1或0。变量prnt被储存在int大小的内存单元中,但是在本例中只使用了其中的4位。

:后面的数字用来限定成员变量占用的位数。位域的宽度不能超过它所依附的数据类型的长度。通俗地讲,成员变量都是有类型的,这个类型限制了成员变量的最大长度,:后面的数字不能超过这个长度。

如上述结构中autfd、bldfc、undin、itals后面的数字不能超过unsigned int的位数,即在32bit环境中就是不能超过32。

位域的取值范围非常有限,数据稍微大些就会发生溢出,请看下面的例子:

#include <stdio.h>

struct pack
{
 unsigned a:2;  // 取值范围为:0~3
 unsigned b:4;   // 取值范围为:0~15
 unsigned c:6;   // 取值范围为:0~63
};

int main(void)
{
 struct pack pk1;
 struct pack pk2;

 // 给pk1各成员赋值并打印输出
 pk1.a = 1;
 pk1.b = 10;
 pk1.c = 50;
 printf("%d, %d, %d\n", pk1.a, pk1.b, pk1.c);

 // 给pk2各成员赋值并打印输出
 pk2.a = 5;
 pk2.b = 20;
 pk2.c = 66;
 printf("%d, %d, %d\n", pk2.a, pk2.b, pk2.c);

 return 0;
}

程序输出结果为:

pk1.a = 1, pk1.a = 10, pk1.c = 5
pk2.a = 1, pk2.b = 4, pk2.c = 2

显然,结构体变量pk1的各成员都没有超出限定的位数,能够正常输出。而结构体变量pk2的各成员超出了限定的位数,并发生了上溢(溢出中的一种),关于溢出的概念可查看往期笔记:【C语言笔记】整数溢出

C语言标准规定,只有有限的几种数据类型可以用于位域。在ANSI C 中,这几种数据类型是signed intunsigned int;到了C99、C11新增了_Bool的位字段。关于C语言的几套标准可查看往期笔记:【C语言笔记】什么是ANSI C标准?

二、位域的存储

位域的存储同样遵循结构体内存对齐的规则,关于结构体内存对齐的问题可查看往期笔记:【C语言笔记】C语言结构体内存对齐问题

看一个例子:

#include <stdio.h>
struct pack
{
 unsigned a:2;  
 unsigned b:4;   
 unsigned c:6;   
};
int main(void)
{
 printf("sizeof(struct pack) = %d", sizeof(struct pack));
 return 0;
}

程序输出结果为:

sizeof(struct pack) = 4

这是因为,a、b、c成员所占的位长之和在一个存储单元(此处为unsigned类型所占的字节数)内,即4个字节内,所以struct pack类型的变量所占的字节长度为4个字节(实际a、b、c一共占用12bit,还有20bit空间为保留的空白)。

可能有人有疑问,此处a、b、c加起来一共才12bit,两个字节都不到,那么只需要,2个字节不就好了吗。这就是因为内存对齐搞的鬼,此处要将内存对齐到 4 个字节(unsigned类型所占的字节数),以便提高存取效率。

假如把该结构声明改为:

struct pack
{
 unsigned a:12;  
 unsigned b:24;   
 unsigned c:6;   
};

那么,输出结构应该为什么呢?

输出结果为:

sizeof(struct pack) = 8

因为此时a成员单独占一个内存单元(4字节),b、c成员紧挨着占下一个内存单元(4字节),所以结果为8字节,这也是因为内存对齐。a、b、c占用内存的示意图如:

其中,空白部分为保留的空白填充内存。这里的空白内存是系统自动留出的,同时,我们也可以自己留出填充内存。如无名位域就可以用来作填充:

struct pack
{
unsigned a:12;  
unsigned  :20;//该位域成员不能使用,用于填充
unsigned c:6;   
};

无名位域一般用来作填充或者调整成员位置。因为没有名称,无名位域不能使用。
上面的例子中,如果没有位宽为 20 的无名成员,a、c 将会挨着存储,sizeof(struct pack) 的结果为 4;有了这 20 位作为填充,a、c将分开存储,sizeof(struct pack)的结果为 8。

位域也是个很重要的知识点,如在DSP2803X的固件库的一些结构封装中普遍用到这样的写法:

以上就是关于位域的一些笔记,如有错误,欢迎指出!


我的个人博客:https://zhengnianli.github.io/

我的微信公众号:嵌入式大杂烩


 上一篇
【C语言笔记】操作位的技巧 【C语言笔记】操作位的技巧
一、操作位的方法操作位有两种方法,一种是位字段,另一种是使用按位运算符。位字段的方法可查看往期笔记:【C语言笔记】位域。本文介绍使用按位运算符操作位的方法。下表为几种位操作符及其含义: 二、不改变其他位的值的状况下,对某几个位进行设值。在
2019-02-15
下一篇 
【RT-Thread笔记】RT-Thread启动过程 【RT-Thread笔记】RT-Thread启动过程
我们学习编程,特别是嵌入式编程,不仅仅要多写代码进行练习,还要多看看一些例程。最近在学习RT-Thread,原子的某例程的的主函数如下(这是在keil5下的截图): 这是主函数中的全部代码,主要是创建一个led线程并启动。那么问题来了,要
2019-01-23
  目录