C语言联合体 union 怎么用?内存布局和实战场景详解
联合体(union)的所有成员共享同一块内存,大小等于最大成员的大小(再按最严格对齐要求补齐)。和 struct 每个成员各占各的不同,union 同一时刻只有最后写入的成员是有效的——读其他成员是未定义行为(C99 附录 J 明确标注)。
三个核心使用场景:类型双关(不经过指针强转,用 union 做浮点数的二进制级操作)、变体类型(配合 enum 标记当前存的是哪种数据,俗称 tagged union)、协议解析(同一段内存既能当原始字节流读,也能按字段结构体读)。
c// 最常见的实用写法:tagged union enum Tag { TAG_INT, TAG_FLOAT, TAG_STR }; struct Value { enum Tag tag; union { int i; float f; char *s; } data; };
一个冷知识:union 常用来检测字节序。写入 int x = 1,然后读 char 成员,如果是 1 就是小端序——很多 libc 的 endian 检测就是这么实现的。
追问
union 和 struct 的内存布局有什么区别?
struct 的成员顺序排列,总大小是各成员大小之和(加上对齐填充)。union 的成员重叠排列,所有成员起始地址相同,总大小取最大成员再按最大对齐补齐。所以 struct 是"加法",union 是"取最大值"。
读非最后写入的成员为什么是 UB?
C 标准只保证读最后写入的成员是有定义的。因为不同成员可能有不同的位表示(trap representation),编译器有权假设你不会这么干,从而做激进优化。不过实践中,用 union 做 type punning(比如写 float 读 unsigned int 看位模式)在 GCC/Clang 下是扩展支持的行为,C99 TC3 之后的标准草案也倾向允许,但严格来说仍不算完全可移植。
union 的对齐规则是什么?
union 的对齐要求等于其最严格成员的对齐要求。比如包含 double 的 union 在 64 位系统上按 8 字节对齐,即使最大成员只占 4 字节,最终 sizeof 可能是 8 而不是 4。可以用 _Alignof(C11)或 __alignof__ 查实际对齐。
实际项目里怎么安全地用 union?
永远搭配 enum 标记使用(tagged union 模式)。写入某个成员前先设好 tag,读取前先检查 tag——任何跳过 tag 检查直接访问 union 成员的代码都是定时炸弹。C 语言没有内置的 sum type,tagged union 就是最接近的替代。