联合体(共用体) 和 位域 的使用详细解析
联合体(共用体示例):
该联合体使用了位域
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_flag
、sta
和 unused
。这些成员变量使用了位域(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
,我们可以通过以下两种方式访问数据:
-
使用
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。
这样设计的目的是在有限的位数内表示多个状态,以节省存储空间。然而,如果需要表示更多的状态,就需要增加位域的位数或者其他的数据结构来进行扩展。
需要注意的是,在使用位域时,位域的位数和取值范围需要根据具体的需求进行设计,并且要确保符合预期的取值范围。