单片机编程高效利用RAM资源的方法(2)

控制/MCU

1879人已加入

描述

位域的定义

RAM

位域的定义格式如下:

struct 位域结构体名 
{ 
    数据类型  位域名   :位域长度;
};

标准数据类型

标准C规定位域的数据类型只有如下四种:

  • unsigned int
  • signed int
  • int
  • _Bool

例如;

/*无符号整型位域,范围:0~7*/
unsigned int b:3; 


/*有符号整型位域,范围:-4~3*/
signed int b:3;


/*int 默认是 signed int,范围:0~7 或 -4~3*/
int b:3;


/*布尔类型位域,范围:0~1*/
bool b:1;

注意:

在 ANSI C 中,位域数据类型只有 unsigned int ,signed int,int 三种,到 C99 才加入 _Bool 数据类型支持。

int 用作位域数据类型,表示无符号数时,范围为:07;表示有符号数时,范围为:-43 。

其他数据类型

细心的小伙伴可能已经发现了,在【基础篇】的示例代码里,我用了 unsigned char 类型来定义位域成员,上面不是说标准C规定了只有 unsigned int、signed int、int 和 _Bool 这四种数据类型才能作为位域的数据类型吗?为什么用 unsigned char 类型定义位域成员也行呢?

其实这个问题与编译器有关,单片机应用程序开发,用到的主流 IDE 一般都是 Keil 和 IAR ,这两个 IDE 都集成了编译器,这些编译器大多都在标准C上做了扩展,增加了 char、signed char、unsigned char 等数据类型,所以使用 unsigned char 类型定义位域成员也是没有问题的。

为了方便讲解,下面的例子中我都会使用 unsigned char 类型定义位域成员。

位域名

位域名是可选项,定义位域成员时,是可以不写位域名的,格式如下:

struct 位域结构体名 
{ 
    数据类型     :位域长度;
};

这些没有位域名的位域,叫做无名位域,有些资料也叫匿名位域;无名位域没有位域名,在程序中是无法引用无名位域的,无名位域一般用来填充指定位数或调整成员位置。

位域长度

位域长度就是指某个位域成员变量所占用的二进制位数(bit),位域长度不能超过定义该位域的数据类型的长度。

这个很好理解,假设我定义了一个位域成员 a ,如下:

struct A
{ 
    unsigned char a  :8;
};

位域成员 a 是由 unsigned char 类型定义的,假设 unsigned char 类型占用一字节空间,那么位域成员 a 的位域长度就不能超过 8 。

位域的使用

RAM

位域的使用方法和结构体一样,有如下两种形式:

位域变量名.位域名
位域变量名- >位域名

例如:

struct Bit_Field_t
{
    unsigned char a      :4;
    unsigned char b      :3;
};


struct Bit_Field_t Bit_Field;
struct Bit_Field_t *Bit_Field_p;


Bit_Field.a
Bit_Field_p- >a

需要注意的是,位域一般只占用一个字节的若干个 bit(例如上述的位域 a只占用了一个字节的 4bit 空间),而且不一定位于字节的起始位置。另外,地址的操作单位是字节,而不是 bit ,因此对获取位域的地址是没有意义的,例如下面的操作是不被允许的:

&Bit_Field.a

C11 中还规定,不能对位域使用 sizeof 和 _Alignas 这两个关键字。

位域的存储

RAM

测试环境

先说明一下我的测试环境,下面涉及到的代码,是在ubuntu 系统下,使用 GCC 编译的。

RAM

测试各常用变量长度如下:

printf("sizeof(char)   = %dn", sizeof(char));
printf("sizeof(short)  = %dn", sizeof(short));
printf("sizeof(int)    = %dn", sizeof(int));
printf("sizeof(long)   = %dn", sizeof(long));


printf("n");


printf("sizeof(unsigned char)   = %dn", sizeof(unsigned char));
printf("sizeof(unsigned short)  = %dn", sizeof(unsigned short));
printf("sizeof(unsigned int)    = %dn", sizeof(unsigned int));
printf("sizeof(unsigned long)   = %dn", sizeof(unsigned long));

RAM

相邻位域数据类型相同

当相邻的位域数据类型相同时,各个位域都是紧挨着存储的,如果剩余空间不足以存放下一个位域,则下一个位域从新的存储单元开始存储,例如:

struct Bit_Field_t
{
    unsigned char a      :4;
    unsigned char b      :3;
    unsigned char c      :5;
};


struct Bit_Field_t Bit_Field;

位域 a 和位域 b 占用了 7bit 空间,剩余 1bit 空间不足以存储位域 c ,所以位域 c 从下一存储空间开始存储,unsigned char 类型占用一字节空间,所以位域 c 从下一字节开始存储,如下:

RAM

相邻位域数据类型不同

当相邻的位域数据类型不同时,不同数据类型的位域可能紧挨着存储,也有可能从下一个存储单元开始存储,这个存储机制与编译器有关,一般来说,GCC 编译器会将不同数据类型的相邻位域紧挨着存储,例如:

struct Bit_Field_t
{
    unsigned char  a      :4;
    unsigned char  b      :3;
    unsigned short c      :5;
};


struct Bit_Field_t Bit_Field;

位域 a 和位域 b 类型相同,没有超出字节长度,紧挨着存储,剩余 1bit 空间,位域 c 与位域 b 类型不同,如果编译器将这两个相邻位域紧挨着存储的话,示意图如下:

RAM

位域之间有无名位域

当两个位域之间有无名位域时,位域的存储位置与无名位域长度有关。

当无名位域长度为 0 时,指定下个位域从下一个存储单元开始存放,例如:

struct Bit_Field_t
{
    unsigned char a : 3;
    unsigned char   : 0;
    unsigned char b : 4;
};


struct Bit_Field_t Bit_Field;

unsigned char 类型占用一字节空间,位域 a 占用了 3bit 空间,紧挨着就是一个长度为0的无名位域,这就意味着位域 b 需要从下一个存储单元开始存储,unsigned char 类型占用一字节空间,所以位域 b 从下一字节开始存储。

RAM

当无名位域长度大于 0 时,跳过指定 bit 后再开始存放下一位域,例如:

struct Bit_Field_t
{
    unsigned char a : 3;
    unsigned char   : 4;
    unsigned char b : 4;
};


struct Bit_Field_t Bit_Field;

unsigned char 类型占用一字节空间,位域 a 占用了 3bit 空间,紧挨着就是一个长度为4的无名位域,此时会跳过 4bit 不用,在 bit7 的位置开始存放位域 b 。

RAM

小结

RAM

位域的优点很明显,合理运用的话可以极大地节省内存资源;然而,位域的缺点也同样明显,不同的平台,不同的编译器,不同的大小端模式,都会影响位域在内存中的存储,因此,使用位域的程序,想在不同的平台间移植,是件很头疼的事情。

上面举的例子,都是以单个字节作为存储单元,以字节对齐的方式为前提讲解的,在使用其他长度的类型作为存储单元以及使用其他对齐方式都会有不同的结果,这个大家在实际开发中需要灵活应用。

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分