C语言位域在嵌入式中的应用

位域的概念

  位域(或者也能称之为位段,英文表达是 Bit field)是一种数据结构,可以把数据以位元的形式紧凑的存储,并允许程序员对此结构的位元进行操作。这种数据结构的好处是:可以使数据单元节省存储空间,当程序需要成千上万个数据单元时,这种数据结构的优点也就很明显地突出出来了。位段可以很方便地访问一个整数值的部分内容从而简化程序源代码。

位域的定义

  总体来说位域的定义可以分为两大类,

  • 一个是结构体位域
  • 一个是共用体体位域

  由于共用体和结构体两者在定义上的形式都是相同的,因此对于位域的定义从形式上看,两者也都是相同的。

结构体位域

  结构体位域定义的一般形式如下所示:

typedef struct
{
    数据类型 位域名 :长度;
}name_Struct;

  举例来说

struct example0
{
    unsigned char x : 3;
    unsigned char y : 2;
    unsigned char z : 1;
}ex0_t;

  上述定义是什么意思呢,用一张图就能很清楚地明白,下图是所定义的结构体位域在内存中的存储位置:
在这里插入图片描述
  从图中我们可以看出,虽然 x 的类型是 unsigned char ,但是并没有占 8 个位,而是占了 3 个位,其取值范围也变成了 0 ~ 2^3-1。
通过上述图片我们也可以猜到这个结构体位域的大小,笔者通过 printf 函数输出结构体位域的大小为:

The Value of sizeof(ex0_t) is : 1 byte

共用体位域

  共用体位域定义的一般形式跟结构体定义的一般形式是大致相同的,直接举一个简单的例子进行说明:

union example1
{
    unsigned char x : 3;
    unsigned char y : 2;
    unsigned char z : 1;
}ex1_u;

  同样的,笔者在这里给出共用体位域在内存中的存储位置:
在这里插入图片描述
  这里笔者也给出共用体位域的大小:

The Value of sizeof(ex1_u) is : 1 byte

  由此也可以得出共用体位域大小遵循的原则是:共用体位域的总大小为最大基本类型成员的大小

结构体位域详解

位域的类型使用无符号型

  正如标题所示,在位域的使用过程中使用无符号的数据类型,下面给出一个例子来说明这个例子:

    struct BitField_8
    {
        char a : 2;
        char b : 3;
    }BF8;
    
    BF8.a = 0x3;/* 11 */
    BF8.b = 0x5;/* 101 */
    printf("%d,%d\n",BF8.a,BF8.b);

  上述的输出结果为

-1,-3

  输出结果并不是我们想要的,究其原因,实际上是因为 BF.a ,BF.b 都是有符号的,那么自然也就有符号位的存在,而最高位为 1 代表负数,负数又是以补码的形式存储在计算机中的,所以也就有了上述的结果。
因此为了避免上述这种问题的出现,应该将 BitField_8 中的 char 转换成 unsigned char ,那输出的结果就是 3,5

位域禁止的操作

  • 由于位域的特殊,同时也有了一些跟普通变量不同的特性:结构体位域成员不能够使用取址操作

  • 结构体位域成员不能够用 static 修饰

  • 结构体位域成员不能够使用数组

成员大小之和超过一个基本存储空间

  除了上述成员不同类型对于不同编译器有不同的处理方式,当成员大小之和超过一个基本存储空间时,不同的编译器也有不同的处理方式,比如下面这段代码:

struct short_flag_t
{
    unsigned short a : 7;
    unsigned short b : 10;    
};

  对于上面这段代码,同类型成员除了这样定义之外,也可以这样定义:

struct short_flag_t
{
    unsigned short a : 7,/*注意此处是逗号*/
                   b : 10;
};

  上面的代码因为 unsigned short 的大小是 2 个字节,而成员 a,b加起来的大小已经超过了 2 个字节,所以这种情况下也就有了以下两种存储方式:
a , b 紧邻b
  在下一个可存储它的存储单元内分配内存

  不同编译器可能面对这种情况会采用不同的存储方式,对于 GCC 来说,采用的是第二种,如果编译器采用的是第一种方式,而程序要求又需要按照第二种方式来进行存储,又该如何办呢?这时就要利用匿名 0 长度位域字段的语法强制位域在下一个存储单元存储,示例代码如下:

struct short_flag_t
{
    unsigned short a : 2;
    unsigned short   : 0;
    unsigned short b : 3;
}

  上述代码对于 a , b 来讲,b 便不会紧挨着 a 进行存储,而是强制使 b 在下一个存储单元进行存储。

位域的应用

  上述便是位域涉及的基本概念,那知道了基本概念之后,又能使用位域做些什么呢?最容易另人想到的就是使用结构体位域定义标志位,由于我们在裸机开发的过程中,没有信号量,事件等机制,通常会定义一些范围只存在于 0~1 的开关量,而在没有使用位域之前,最小的变量类型都是 1 个字节,使用结构体位域将能够根据取值范围定义该变量的位数,从而起到节省内存的作用。

用于访问微控制器的寄存器

  位域受到处理器和编译器的影响,在使用前我们必须清楚当前处理器是大端对齐还是小端对齐,必须清楚当前编译器对所定义的位域有何影响

  如果我们现在要使用位域访问一个 8 位的寄存器,这个寄存器大致长这个样子:
在这里插入图片描述
  那么我们就可以使用结构体位域构造这样一个数据结构:

typedef union
{
    unsigned char Byte;
    struct
    {
        unsigned char bit012 : 3;
        unsigned char bit34  : 2;
        unsigned char bit5   : 1;
        unsigned char bit6   : 1;
        unsigned char bit7   : 1;
    }bits;
}registerType;

  现在假设我们这个寄存器的地址是 0x0000 8000,那么我们就可以定义一个指针并使其指向这个地址,如下:

registerType *pReg = (registerType *)0x0000 8000;

  在进行了上述定义之后,我们就可以对寄存器进行操作了,首先,我们可以使用位域的方式操作寄存器的位,比如这样:

pReg->bits.bit5 = 1;
pReg->bits.bit012 = 7;

  当然也可以利用 union 的特性直接操作整个寄存器,如下:

pReg->Byte = 0x55;

  使用位域完成对于寄存器的访问,对于上述例子来讲,我们必须要注意的一点是此例子是基于小端对齐模式的。

  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值