联合体(共用体) 和 位域 的使用详细解析

本文详细介绍了如何在C语言中使用联合体和位域来节省内存空间,通过定义一个包含status和位域结构的联合体,展示了如何利用位域的特性进行数据存储和操作。同时提到了编译器兼容性和位域布局的重要性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

联合体(共用体) 和 位域 的使用详细解析

联合体(共用体示例):

        该联合体使用了位域

typedef union
{
	uint16_t status;
    struct {
    	uint16_t tick_flag:1,
		sta:2,
		unused:13;
    };
} holding_reg_dev_status_t;

        该联合体包含一个16位的无符号整数status和一个结构体struct,结构体中又包含了三个成员变量:tick_flag、sta和unused。

        其中tick_flag为1位,sta为2位,unused为13位。通过使用位域(bit-field),可以将结构体中的各个成员变量存储在不同的位上,节省内存空间。在本例中,tick_flag占用了status中的最低一位(即第0位),sta占用了status中的第1至2位,unused占用了status中的第3至15位。

        通过联合体,可以让status和struct共享同一段内存地址,从而实现对同一数据的不同解释。由于status和struct共享同一段内存,因此修改一个变量的值会影响到另一个变量的值。在本例中,可以通过修改status来同时改变struct中各个成员变量的值,也可以通过修改struct中的成员变量来改变status的值。

示例代码如下:

#define TICK_FLAG_MASK 0x0001
#define STA_MASK       0x0006

holding_reg_dev_status_t holding_reg_dev_status;

void setup()
{
    // 初始化状态变量
    holding_reg_dev_status.status = 0;
    holding_reg_dev_status.tick_flag = 1;
    holding_reg_dev_status.sta = 2;
}

void loop()
{
    // 读取并修改状态变量的值
    uint16_t status = holding_reg_dev_status.status;
    status &= ~TICK_FLAG_MASK;  // 清除tick_flag位
    status |= STA_MASK;        // 设置sta位
    holding_reg_dev_status.status = status;
}

在上述示例中,首先通过holding_reg_dev_status.status = 0来初始化状态变量holding_reg_dev_status。然后,在loop()函数中,使用按位与(&)和按位或(|)操作符来读取并修改状态变量的值。通过将TICK_FLAG_MASK取反(即~TICK_FLAG_MASK),再与状态变量进行按位与操作,可以清除tick_flag位。通过将STA_MASK与状态变量进行按位或操作,可以设置sta位。最后,将修改后的status值赋给holding_reg_dev_status.status,从而更新联合体中的各个成员变量的值。


详细解析该联合体:

typedef union
{
	uint16_t status;
    struct {
    	uint16_t tick_flag:1,
		sta:2,
		unused:13;
    };
} holding_reg_dev_status_t;

        这段代码定义了一个联合体 holding_reg_dev_status_t,它包含了一个16位无符号整数 status 和一个结构体。

        首先,我们来看结构体部分的定义:

struct 
{ 
    uint16_t tick_flag:1,
    sta:2,
    unused:13; 
};

        该结构体中定义了三个成员变量:tick_flagstaunused。这些成员变量使用了位域(bit-field)的方式进行定义。

  • tick_flag 占据了 1 位。
  • sta 占据了 2 位。
  • unused 占据了 13 位。

        接下来,我们来看联合体的定义:

typedef union 
{ 
    uint16_t status; 
    struct{ 
        uint16_t tick_flag:1, 
        sta:2, 
        unused:13; 
    }; 
} holding_reg_dev_status_t;

        该联合体包含了一个16位无符号整数 status 和一个匿名结构体。通过联合体的特性,结构体和 status 共享同一段内存空间。

        这意味着当我们访问 holding_reg_dev_status_t 类型的变量时,可以选择使用 status 这个整数值,也可以使用结构体中的位域成员变量来访问同一段内存。

        例如,假设有一个 holding_reg_dev_status_t 类型的变量 dev_status,我们可以通过以下两种方式访问数据:

  1. 使用 status 整数值:

   dev_status.status = 0xABCD; // 将整数值赋给 status 

   uint16_t value = dev_status.status; // 从 status 中读取整数值

     2. 使用结构体中的位域成员变量:

dev_status.tick_flag = 1;  // 设置 tick_flag
dev_status.sta = 2;        // 设置 sta
uint16_t value = dev_status.unused;  // 读取 unused 的值

        需要注意的是,由于联合体中的结构体使用了位域的方式进行定义,不同编译器可能对位域的实现方式有所不同。因此,在使用位域时需要注意编译器的兼容性和预期的位域布局。


问:struct {

                  uint16_t tick_flag:1,

                   sta:2,

                   unused:13;

        };

         这里面的一位是几个字节 ???

        在这个结构体中,位域的大小是以位(bit)为单位来定义的。一个字节(byte)由8位组成。

根据结构体中的位域定义:

  • tick_flag 占据了1位。
  • sta 占据了2位。
  • unused 占据了13位。

        由于总共占据16位(2个字节),可以将这个结构体看作是一个占据2个字节的位字段(bit-field)。

        需要注意的是,位域的位数并不一定要与字节对齐。编译器会按照自己的规则进行位域的布局和对齐,以满足平台的要求。因此,在使用位域时,要注意编译器的实现规则和预期的位域布局。


问:那这样的位怎么赋值呢,赋值的范围怎么判断呢???

        对于位域(bit-field)的赋值,我们可以使用点号操作符来访问结构体中的位域成员。例如,在上面的结构体定义中,我们可以使用以下方式对位域进行赋值:

holding_reg_dev_status_t dev_status;
dev_status.tick_flag = 1;   // 将 tick_flag 的值设置为 1
dev_status.sta = 2;         // 将 sta 的值设置为 2
dev_status.unused = 0x7FFF; // 将 unused 的值设置为 0x7FFF

        在进行位域赋值时,需要注意位域的位数和取值范围。例如,在上面的结构体定义中,tick_flag 只占据了一个比特,因此只能取值 0 或 1。同理,sta 占据了两个比特,可以取值 0、1、2 或 3。unused 则占据了 13 个比特,可以取值范围为 0x0000 到 0x7FFF。

        需要注意的是,位域的位数不一定要与字节对齐,而是根据编译器的实现规则进行布局。因此,在使用位域时,要注意编译器的实现规则和预期的位域布局。同时,由于位域的实现有一定的灵活性,可能会存在跨平台或跨编译器的兼容性问题,需要谨慎使用。


 问:为什么 sta 占据了两个比特,只可以取值 0、1、2 或 3。不能取值其他吗???

        对于 sta 这个位域,占据了2个比特(bits),因此可以表示的不同取值是有限的。在这个结构体中,sta 的取值范围被限制在 0、1、2 和 3 这四个值。

        这是因为使用2个比特可以表示的不同取值有 2^2 = 4 种可能性,即 0、1、2 和 3。位域的取值范围由位域的位数确定,不能超出这个范围。

在这个例子中,我们可以将不同的 sta 值与相应的含义关联起来,例如:

  • sta = 0 可以表示某种状态 A。
  • sta = 1 可以表示某种状态 B。
  • sta = 2 可以表示某种状态 C。
  • sta = 3 可以表示某种状态 D。

        这样设计的目的是在有限的位数内表示多个状态,以节省存储空间。然而,如果需要表示更多的状态,就需要增加位域的位数或者其他的数据结构来进行扩展。

        需要注意的是,在使用位域时,位域的位数和取值范围需要根据具体的需求进行设计,并且要确保符合预期的取值范围。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

枕上

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值