C 语言 union
更新时间:2026-08-26。本文是
languages/c/主题入门层第 48 篇。同一个内存位置,有时存 int,有时存 float,有时存一组字节——struct让成员"并列共存",union则让成员"共享同一块内存"。它是 C 里最省内存、也最容易被误用的类型。
本文要回答的问题
union和struct有什么本质区别?union占多大内存?成员之间是什么关系?- 它到底有什么用?现实中哪里会用到?
一、基本语法与内存布局
c
union Number {
int i;
float f;
unsigned char bytes[4];
};声明、赋值、访问都跟 struct 一样:
c
union Number n;
n.i = 65;
printf("%d\n", n.i); // 65但内存布局完全不同。struct 是成员依次排开,union 是成员从同一地址开始:
| 对比 | struct | union |
|---|---|---|
| 成员内存 | 依次排列 | 共享同一块 |
| 大小 | ≥ 各成员之和(+填充) | ≥ 最大成员(对齐取整) |
| 同时访问多成员 | 可以,都有意义 | 只有最后写入的有意义 |
| 典型用途 | 记录一组相关数据 | 同一位置多种解释 |
二、大小:取最大成员
c
union Number n;
printf("%zu\n", sizeof(union Number)); // 4:最大成员 int/float 是 4 字节
struct NumberBox {
int i;
float f;
unsigned char bytes[4];
};
printf("%zu\n", sizeof(struct NumberBox)); // 12:成员相加注意对齐规则同样适用:成员最大 8 字节(如 double)时,union 大小可能补到 8 的倍数。
写一个成员,读另一个成员会怎样?读出来的是同一块内存按新类型重新解释的字节——这在普通代码里是未定义行为(类型双关),但在嵌入式中是常规操作,见下文"用途"。
三、典型用途 1:同一内存,多种解释(协议解析)
网络字节序、文件头、寄存器都是"同一段字节,按不同字段解析"的典型:
c
typedef union {
unsigned int raw; // 按 32 位整型整体读
struct {
unsigned int version : 4; // 位域
unsigned int type : 8;
unsigned int len : 20;
} fields;
} PacketHeader;读取网卡收到的原始字节流时,既可以直接 raw 整体处理,也可以按字段访问 fields.version——同一个数据,两种视角。
四、典型用途 2:省内存的"多选一"记录
一个图形程序,形状要么是圆(存半径)、要么是矩形(存长宽),不会同时出现:
c
typedef struct {
int kind; // 0=圆, 1=矩形
union {
float radius;
struct { float w, h; } rect;
} shape; // 只取最大分支的空间
} Shape;kind 字段标明当前 union 里哪个成员有效——这种 union + 类型标签(tagged union) 模式,是省内存的经典做法,也是 Rust 的 enum 在 C 里的手搓版。
五、常见坑对照
| 坑 | 现象 | 对策 |
|---|---|---|
| 写一个读另一个 | 得到"重新解释"的字节,逻辑错 | 只读最后写入的成员 |
| 忘记录类型标签 | 按错误分支解析 | 加 kind 字段 |
| 误以为 union 能同时存多值 | 后写覆盖先写 | 记住"同一时刻只有一个有效" |
| 结构体里 union 大小估算错 | 缓冲区溢出 | 用 sizeof |
| 类型双关(读未写入成员) | UB,编译器可能优化错 | 用 memcpy 或按标准做法 |
六、与本站主线衔接
- 联合体常与typedef 搭配做协议头,与结构体内存对齐 关系密切;
- 位域(
: 4这种写法)在结构体位域展开; - 网络字节序转换(
ntohl/htons)与 union 解析的结合,见网络字节序与协议解析。
一句话总结
union 让所有成员共享同一块内存,大小取最大成员并按对齐取整,同一时刻只有最后写入的成员有意义;典型用途是"同一字节多种解释"(协议/寄存器)和"多选一"省内存(配类型标签)——它是 C 的"内存复用"工具,用得好省内存,用错了读出一堆乱码。