11. 联合体入门:联合体的定义、特点与结构体对比
好,咱们今天聊聊联合体。说实话,联合体在C语言里是个挺有意思的存在。很多人学完结构体就觉得万事大吉了,结果一碰到联合体就懵了——这玩意儿跟结构体长得差不多,怎么用起来完全不是一回事?
我当年刚入行时也踩过这个坑。有一次调试一个通信协议栈,数据包解析死活不对,查了两天才发现——我把联合体当结构体用了。嗯,从那以后,我对联合体就格外上心。
11.1 联合体的定义
联合体,关键字是 union。定义方式跟结构体几乎一模一样:
union Data {
int i;
float f;
char str[4];
};
你看,语法上跟结构体唯一的区别就是 struct 换成了 union。但别被这表象骗了——它们的内存布局完全是两码事。
声明变量也一样:
union Data data; // 普通变量
union Data *pData; // 指针
union Data arr[10]; // 数组
我个人习惯在定义联合体时加上 typedef,用起来更顺手:
typedef union {
int i;
float f;
char str[4];
} Data;
11.2 联合体的核心特点:所有成员共享内存
这才是联合体的灵魂所在。说白了,联合体里所有成员都从同一个内存地址开始存放。你想想看,这意味着什么?
整个联合体的大小,等于它最大成员的大小。
来,看个例子:
#include <stdio.h>
union Test {
char c; // 1字节
int i; // 4字节
double d; // 8字节
};
int main() {
union Test t;
printf("union size = %zu\n", sizeof(t)); // 输出 8
t.d = 3.14;
printf("d = %f\n", t.d); // 3.140000
t.i = 100;
printf("i = %d\n", t.i); // 100
printf("d = %f\n", t.d); // 乱码!因为内存被 i 覆盖了
return 0;
}
为什么会这样?因为当你给 t.i 赋值时,它就把 t.d 占用的那8个字节的前4个给覆盖了。double 的剩余4个字节还是原来的值,但整个数据已经面目全非了。
11.3 联合体 vs 结构体:一张图看懂
咱们用SVG画个对比图,一目了然:
11.4 联合体的典型应用场景
你可能会问:这玩意儿到底有什么用?我项目中遇到过几个典型场景:
场景一:数据解析(协议栈)
比如你从串口收到一个数据包,前1个字节是类型,后面4个字节可能是整数也可能是浮点数。用联合体就特别方便:
typedef union {
uint32_t u32;
int32_t s32;
float f32;
uint8_t bytes[4];
} PacketData;
typedef struct {
uint8_t type;
PacketData data;
} Packet;
收到数据后,根据 type 决定用哪个成员。内存不用重新分配,直接解析就行。
场景二:寄存器位操作
嵌入式里经常要操作硬件寄存器。用联合体+位域,读写都方便:
typedef union {
uint32_t value; // 整体读写
struct {
uint32_t enable : 1; // bit0
uint32_t mode : 2; // bit1-2
uint32_t rate : 3; // bit3-5
uint32_t : 26; // 保留位
} bits;
} ControlRegister;
想整体赋值就写 reg.value = 0x12345678,想单独改某个位就写 reg.bits.enable = 1。我在做STM32驱动时经常这么用。
场景三:节省内存
有些场景下,多个变量不会同时用到。比如一个学生管理系统,学生要么是在校生(需要年级信息),要么是毕业生(需要毕业年份)。用联合体就能省内存:
typedef struct {
char name[32];
int id;
union {
int grade; // 在校生:年级
int gradYear; // 毕业生:毕业年份
} status;
} Student;
11.5 避坑指南
我曾经在项目里犯过一个低级错误,差点导致产品延期。分享出来,大家引以为戒:
联合体不会自动初始化所有成员。你给
t.i = 10,然后读 t.f,得到的是垃圾值。这不是随机数,是未定义行为!
如果联合体里有指针,赋值时要格外小心。指针指向的内存不会因为联合体切换成员而自动释放。我曾经因此造成内存泄漏,查了两天才发现。
联合体嵌套在结构体里时,对齐规则可能会让你意外。比如结构体里有个联合体,联合体最大成员是double,那整个结构体的对齐可能就被拉到了8字节。我建议你用
#pragma pack 或 __attribute__((packed)) 显式控制。
11.6 联合体 vs 结构体:对比总结
| 对比维度 | 结构体 struct | 联合体 union |
|---|---|---|
| 内存布局 | 成员顺序排列,各自独立 | 所有成员从同一地址开始 |
| 大小计算 | 各成员大小之和(考虑对齐) | 最大成员的大小 |
| 成员使用 | 可同时使用所有成员 | 同一时刻只能使用一个成员 |
| 初始化 | 可初始化所有成员 | 只能初始化第一个成员 |
| 典型用途 | 描述"同时具有"的复合数据 | 描述"互斥选择"的变体数据 |
| 内存效率 | 较低(每个成员都占空间) | 较高(共享空间) |
| 使用风险 | 较低(成员独立,不易出错) | 较高(误读未赋值成员会出问题) |
嗯,说到这,联合体的核心内容基本就这些了。记住一句话:结构体是「和」的关系,联合体是「或」的关系。 搞清楚了这一点,你就掌握了联合体的精髓。
下次写代码时,遇到那种「要么是A,要么是B,不会同时出现」的数据,不妨试试联合体。用对了地方,它能让你的代码既省内存又清晰。