C 语言联合体

内容简介

union 让多个成员共享同一块存储空间。它适合表达“同一段内存,在不同情况下保存不同类型的数据”,常见于解释器值对象、协议解析、硬件接口、节省内存的嵌入式数据结构。

联合体不是“结构体的省内存版本”。它的核心语义是:多个成员重叠,同一时刻通常只有一个成员是当前有效含义。

用法

基本定义

typedef union Number {
    int i;
    float f;
} Number;
 
Number n;
n.i = 42;

联合体大小至少能容纳最大成员,并满足最严格成员的对齐要求。

成员共享存储

Number n;
n.i = 42;
n.f = 3.14f;  // 写入 f 后,i 不再代表原来的有效含义

写入一个成员后,再按另一个不兼容成员读取,通常不应该作为可移植写法。

带标签联合体

直接使用 union 容易忘记当前有效成员,工程中常配合枚举保存类型标签。

typedef enum ValueKind {
    VALUE_INT,
    VALUE_DOUBLE
} ValueKind;
 
typedef struct Value {
    ValueKind kind;
    union {
        int i;
        double d;
    } data;
} Value;

读取时先判断标签:

void value_print(const Value *value)
{
    if (value == NULL) {
        return;
    }
 
    switch (value->kind) {
    case VALUE_INT:
        printf("%d\n", value->data.i);
        break;
    case VALUE_DOUBLE:
        printf("%f\n", value->data.d);
        break;
    }
}

匿名联合体成员

C11 支持匿名联合体成员,常用于减少一层 .data 访问。

typedef struct Token {
    int kind;
    union {
        int int_value;
        double double_value;
    };
} Token;
 
Token token;
token.int_value = 10;

注意项目编译标准和编译器支持。

最佳代码实践

  • 联合体优先配合枚举标签使用,明确当前有效成员。
  • 对外接口不要让调用者猜联合体当前保存什么。
  • 如果联合体成员包含资源指针,要明确初始化、销毁和所有权规则。
  • 不要把联合体当成通用类型转换工具;类型转换优先用清楚的转换函数。
  • 用于协议或文件格式时,优先显式解析字节,不要直接依赖联合体布局。

错误用法

typedef union Number {
    int i;
    float f;
} Number;
 
Number n;
n.i = 42;
printf("%f\n", n.f);  // 错误倾向:按另一个成员解释不可靠
typedef struct Value {
    int kind;
    union {
        int i;
        char *s;
    } data;
} Value;
 
Value value;
printf("%s\n", value.data.s);  // 错误:没有确认当前有效成员和指针有效性

注意事项

  • 联合体所有成员地址通常相同,但类型含义不同。
  • 联合体的大小不是所有成员大小之和,而是至少容纳最大成员。
  • 联合体可用于节省内存,但会增加“当前到底是什么类型”的维护成本。
  • 在 C++ 中,联合体成员如果有非平凡构造/析构会引入更多生命周期规则;这已超出 C 主线。

相关:C语言结构体C语言类型转换规则未定义行为

学习路径