一、共用体是什么?和结构体有什么区别?
很多人会把共用体和结构体混淆,其实它们的核心差异在于内存分配方式:
- 结构体(struct):每个成员单独分配独立内存,总大小是所有成员大小之和。
- 共用体(union):所有成员共享同一块内存空间,同一时间只能有效保存其中一个成员的数据。
举个形象的例子:结构体就像一间被隔成多个小房间的公寓,每个房间住不同的人;共用体则是一间多功能房,白天当办公室,晚上当卧室,同一时间只能做一件事。
二、共用体的3个核心特点
- 内存大小等于最大成员的大小:比如一个共用体里有int(4字节)和char buf4(4字节),那这个共用体的大小就是4字节,刚好能容纳最大的成员。
- 成员互斥,修改一个覆盖其他:给共用体的一个成员赋值后,其他成员的值会被覆盖,因为它们共享同一块内存。
- 适合数据类型转换与拆分:可以把一个整数拆成字节数组,也可以把字节数组拼成整数,在串口协议解析、传感器数据处理中特别好用。
三、基础代码示例:理解内存共享
我们先看一个最基础的共用体示例,直观感受它的内存共享特性:
c
#include <stdio.h>
// 定义共用体:int和char buf[4]共享4字节内存
union Data
{
unsigned int num; // 4字节
unsigned char buf[4]; // 4字节
};
int main(void)
{
union Data val;
val.num = 0x12345678; // 给int成员赋值
printf("第0字节: 0x%02X\n", val.buf[0]);
printf("第1字节: 0x%02X\n", val.buf[1]);
return 0;
}
运行结果:
text
第0字节: 0x78
第1字节: 0x56
这说明在小端模式的单片机中,整数的低字节存在低地址,高字节存在高地址,共用体直接帮我们完成了整数到字节的拆分。
四、嵌入式实战场景:串口协议解析
在单片机开发中,串口通信经常需要把整数拆成字节发送,或者把接收到的字节拼成整数,用共用体可以避免手动位运算,代码更简洁高效。
场景1:整数拆分成字节发送
c
#include <stdio.h>
#include <string.h>
// 定义共用体:int和字节数组共享内存
typedef union {
unsigned int data;
unsigned char bytes[4];
} IntToBytes;
int main()
{
IntToBytes converter;
converter.data = 0xAABBCCDD;
// 直接通过bytes成员获取拆分后的字节
printf("拆分后的字节:\n");
for(int i=0; i<4; i++){
printf("字节%d: 0x%02X\n", i, converter.bytes[i]);
}
// 模拟串口发送:依次发送converter.bytes[0]到converter.bytes[3]
return 0;
}
场景2:字节拼成整数接收
c
#include <stdio.h>
#include <string.h>
typedef union {
unsigned int data;
unsigned char bytes[4];
} BytesToInt;
int main()
{
BytesToInt converter;
// 模拟接收到的字节流
unsigned char recv_buf[4] = {0xDD, 0xCC, 0xBB, 0xAA};
// 把接收到的字节复制到共用体的bytes成员
memcpy(converter.bytes, recv_buf, 4);
// 直接通过data成员获取拼接后的整数
printf("拼接后的整数:0x%08X\n", converter.data);
return 0;
}
五、进阶技巧:共用体+结构体嵌套操作寄存器
在单片机中,我们经常需要操作硬件寄存器,既要能整体读写寄存器,又要能单独修改某一个位,这时候用共用体+结构体嵌套的方式会非常方便:
c
#include <stdio.h>
// 定义结构体:拆分寄存器的各个位
typedef struct {
unsigned char bit0 : 1; // 位0
unsigned char bit1 : 1; // 位1
unsigned char bit2 : 1; // 位2
unsigned char bit3 : 1; // 位3
unsigned char reserved : 4; // 保留位
} RegBits;
// 定义共用体:整体寄存器和位结构体共享内存
typedef union {
unsigned char reg_val; // 整体寄存器值
RegBits bits; // 拆分后的位
} Register;
int main()
{
Register reg;
reg.reg_val = 0x03; // 整体赋值
// 单独修改位2,不影响其他位
reg.bits.bit2 = 1;
printf("修改后的寄存器值:0x%02X\n", reg.reg_val);
return 0;
}
六、使用共用体的注意事项
- 不要同时读取多个成员:因为成员共享内存,同时读取会得到错误的值,要确保当前只有一个成员是有效的。
- 注意字节序问题:不同单片机可能是大端或小端模式,跨设备通信时要统一字节序。
- 内存对齐:共用体的大小会对齐到最大成员的对齐数的整数倍,比如如果有double成员,大小会对齐到8字节。
七、总结
共用体是嵌入式C语言中非常实用的技巧,它能帮你在内存有限的单片机中节省RAM,同时简化数据类型转换和协议解析的代码。在串口通信、传感器数据处理、寄存器操作等场景中,合理使用共用体能让你的代码更简洁、更高效。
如果你觉得这篇文章对你有帮助,欢迎点赞收藏,也可以在评论区分享你使用共用体的实战经验!