C 语言重拾【九】联合类型 union
在 C 语言中,union 常被翻译为“联合”或者“联合体”。它和 struct 一样都可以声明多个成员,但两者的内存模型并不相同。union 的关键不在于“同时拥有多个成员”,而在于多个成员共用同一块内存。
因此,union 更适合用在内存复用和底层数据解释这类场景中。
什么是 union
先看一个最简单的声明:
1 | union Example { |
这个 union 看起来有两个成员:i 和 f。但与 struct 不同的是,它们并不会分别占据独立空间,而是共享同一块内存。
如果是结构体:
1 | struct Example { |
那么 i 和 f 都有各自独立的空间;而在 union 中,编译器只会分配一块足够容纳“最大成员”的内存。
可以总结为:
struct:每个成员都有自己的存储空间union:所有成员共享一份存储空间
union 和 struct 的核心区别
两者的区别可以概括为:
struct:把多个字段组合在一起union:给同一块内存提供多种解释方式
假设 int 和 float 都占 4 个字节,那么:
1 | struct Example { |
一般需要 8 个字节左右的空间(实际大小还可能受对齐影响)。
而:
1 | union Example { |
一般只需要 4 个字节,因为 i 和 f 复用了同一块内存。
给一个成员赋值,另一个成员会发生什么
这里需要明确一个问题:既然 i 和 f 使用的是同一块内存,那么给 i 赋值之后,f 会发生什么?
更准确地说:
- 你给
i赋值时,是把这块内存按int的格式写了一遍 - 之后去读
f,是把同一串比特位按float的格式解释
举个例子:
1 | union Example e; |
此时内存里保存的是“整数 1”的二进制表示。如果再去读:
1 | printf("%f\n", e.f); |
你读到的不是 1.0,而是“把整数 1 的位模式当作浮点数解释后的结果”。
所以,union 里并不是“两个成员同时各自拥有一个值”,而是:
只有一份原始内存内容,你选择用哪种类型的视角去看它。
一个更直观的例子
下面看一段代码:
1 |
|
执行顺序是:
- 先把内存写成整数
1 - 再把同一块内存改写成浮点数
3.0f
因此,最后留下来的实际上是 3.0f 的位模式。此时:
e.f一般会打印3.000000e.i打印的是“把3.0f的位模式当成整数解释后的值”
也就是说,最后写入哪个成员,这块内存通常就更接近按哪种类型来解释。
union 最常见的误区
union 在使用时有两个常见误区。
误区一:它适合拿来同时保存多个字段
这种理解并不准确。因为它只有一份内存,任一时刻通常只应把它当成一种成员类型来使用。
误区二:读另一个成员,就是“自动转换类型”
这也不对。union 不会帮你做数值转换,它只是让你用另一种方式解释相同的位模式。比如:
int 1和float 1.0- 它们的内存表示并不相同
因此,把一个成员写进去,再从另一个成员读出来,通常不会得到直观意义上的数值结果。
union 在什么场景下适用
既然 union 这么容易误用,它真正适合什么场景?
1. 互斥数据的内存复用
如果一组字段在任意时刻只会有一个有效值,那么 union 很适合节省内存。
例如:
1 | union Value { |
如果这个值对象在某一时刻只可能表示整数、浮点数或字符串中的一种,那么没必要为三种情况都分配独立空间。
2. 二进制数据、协议、寄存器解析
这是 union 非常经典的用途。
比如我们有一段 4 字节数据,有时想按整数看,有时想按字节数组看:
1 | union Data { |
这类写法在以下场景很常见:
- 网络协议
- 文件格式解析
- 嵌入式开发
- 设备寄存器访问
这些场景的共同特点是:程序员非常在意底层字节布局。
3. 配合标签字段表示“当前有效值”
这是在 C 里使用 union 的最稳妥姿势之一。
因为 union 自己并不知道当前哪个成员有效,所以实际开发里常常会额外配一个标记字段:
1 | enum ValueType { |
使用时先判断 type,再决定读 data.i 还是 data.f。
例如:
1 |
|
这里的 type 就是在告诉我们:当前这块 union 内存应该按什么方式解释。
使用 union 时的注意点
union 虽然强大,但也意味着程序员需要自己承担更多责任。
1. 要明确“当前有效成员”
如果没有配套的标记字段,就很容易写错。例如:
1 | u.i = 42; |
这类代码往往较难维护,也不利于从阅读层面判断作者的真实意图。
2. 它更适合底层场景,而不是普通业务逻辑
普通逻辑代码里,struct 往往比 union 更直观、更安全。只有当你真的需要:
- 节省内存
- 复用同一块存储
- 解析底层位模式
再考虑使用 union。
3. 不要把它当作“自动类型转换工具”
union 的本质不是转换,而是“重新解释内存”。如果只是想做普通数值转换,应该使用显式转换,而不是依赖 union。
总结
理解 union,最重要的是记住下面这句话:
union不是同时保存多个值,而是给同一块内存提供多种解释方式。
因此,union 最适合的场景主要有三类:
- 互斥数据的内存复用
- 底层二进制、协议和寄存器解析
- 配合标签字段实现变体数据结构
如果只是编写普通业务代码,union 往往不是首选;但在处理底层内存布局、位模式或者资源受限场景时,它仍然是 C 语言中非常有价值的一种工具。