C语言联合体详解:共用内存空间的灵活数据结构

C语言联合体详解:共用内存空间的灵活数据结构

在C语言中,有时我们需要一种能够根据不同场景表示不同数据类型的数据结构。例如,要表示水果的“量”,有时需要用整数(6个苹果),有时需要用浮点数(1.5公斤草莓)。C语言提供的**联合体(Union)**结构正是为此而生,它允许多个成员共用同一块内存空间,实现了内存的高效复用。

一、什么是联合体?

联合体是一种特殊的数据结构,它的所有成员共用同一块内存空间。这意味着在任何时刻,只有一个成员的值是有效的,因为写入一个新成员的值会覆盖之前成员的值。

C
1
2
3
4
5
union quantity {
short count; // 用于计数(如:6个苹果)
float weight; // 用于重量(如:1.5公斤草莓)
float volume; // 用于体积(如:2.0升果汁)
};

上面的代码定义了一个名为quantity的联合体,它包含三个成员:count(短整型)、weight(浮点型)和volume(浮点型)。这三个成员共用同一块内存,你只能同时使用其中一个。

二、联合体的内存布局

联合体的核心特性是所有成员共享内存。它的内存大小等于最大成员的大小。以quantity联合体为例:

  • short count:通常2字节
  • float weight:通常4字节
  • float volume:通常4字节

所以quantity联合体的大小为4字节(最大成员float的大小)。

C
1
2
3
4
5
6
7
8
9
10
11
12
13
#include <stdio.h>

union quantity {
short count;
float weight;
float volume;
};

int main() {
union quantity q;
printf("联合体大小:%zu字节\n", sizeof(q)); // 输出:联合体大小:4字节
return 0;
}

这种内存共享机制使得联合体非常节省空间,特别适合那些“要么/要么”的场景——某个时刻只需要一种表示方式。

三、如何使用联合体?

联合体的使用方式与结构体类似,但需要注意内存共享的特性。

1. 声明和初始化

C
1
2
3
4
5
6
7
8
9
// 写法一:先声明后赋值
union quantity q;
q.count = 4;

// 写法二:初始化时指定成员
union quantity q = {.count = 4};

// 写法三:初始化第一个成员
union quantity q = {4};

2. 访问成员

C
1
2
3
4
5
6
7
union quantity q;
q.count = 4;
printf("count is %i\n", q.count); // count is 4

q.weight = 0.5;
printf("weight is %f\n", q.weight); // weight is 0.5
// 注意:此时q.count的值已被覆盖,访问它可能无意义

重要提醒:为联合体的一个成员赋值后,其他成员的值就被覆盖了。因此,只有最后被赋值的成员才有意义。

四、联合体与结构体的区别

特性 结构体(Struct) 联合体(Union)
内存使用 每个成员有独立内存空间 所有成员共用同一内存空间
内存大小 所有成员大小之和(考虑对齐) 最大成员的大小
同时访问 所有成员可同时有效 同一时间只有一个成员有效
适用场景 需要同时保存多个相关数据 同一数据的不同表示方式

示例对比

C
1
2
3
4
5
6
7
8
9
10
11
12
13
// 结构体:每个学生有姓名、年龄、分数,所有信息都需要保存
struct student {
char name[20];
int age;
float score;
};

// 联合体:一个值可以是整数、浮点数或字符串中的一种
union value {
int i;
float f;
char str[20];
};

五、联合体指针操作

联合体支持指针操作,使用方式与结构体类似:

C
1
2
3
4
5
6
7
union quantity q;
q.count = 4;

union quantity* ptr;
ptr = &q;

printf("%d\n", ptr->count); // 4

联合体指针的类型取决于当前正在使用的成员:

C
1
2
3
4
5
6
7
8
9
10
11
12
13
union foo {
int a;
float b;
} x;

int* foo_int_p = (int *)&x; // 当前按int类型解读
float* foo_float_p = (float *)&x; // 当前按float类型解读

x.a = 12;
printf("%d\n", *foo_int_p); // 12

x.b = 3.141592;
printf("%f\n", *foo_float_p); // 3.141592

六、联合体与typedef结合

可以使用typedef为联合体创建类型别名,简化代码:

C
1
2
3
4
5
6
7
8
9
typedef union {
short count;
float weight;
float volume;
} quantity;

// 现在可以直接使用quantity作为类型名
quantity q;
q.count = 10;

七、联合体的实际应用场景

  1. 协议解析:网络协议中同一个字段可能有不同的含义

    C
    1
    2
    3
    4
    union protocol_field {
    uint32_t ip_address; // 作为IP地址时
    uint32_t timestamp; // 作为时间戳时
    };
  2. 类型转换:在不违反严格别名规则的前提下查看数据的二进制表示

    C
    1
    2
    3
    4
    5
    6
    7
    union float_to_int {
    float f;
    uint32_t i;
    } converter;

    converter.f = 3.14;
    printf("浮点数 %f 的二进制表示:0x%08X\n", converter.f, converter.i);
  3. 节省内存:在有限的内存环境中存储多种可能的数据

    C
    1
    2
    3
    4
    5
    union sensor_data {
    int temperature; // 温度传感器数据
    float humidity; // 湿度传感器数据
    char status_code; // 状态码
    };

八、注意事项与最佳实践

  1. 明确当前有效成员:在使用联合体时,应该通过额外的标志位记录当前哪个成员有效。

    C
    1
    2
    3
    4
    5
    6
    7
    8
    struct tagged_union {
    enum { INT_TYPE, FLOAT_TYPE, STR_TYPE } type;
    union {
    int i;
    float f;
    char str[20];
    } value;
    };
  2. 避免未定义行为:访问未赋值的联合体成员会导致未定义行为。

  3. 考虑内存对齐:联合体的大小受最大成员的对齐要求影响。

  4. 跨平台兼容性:不同平台对基本类型的大小可能有不同定义,使用stdint.h中的类型可提高可移植性。

九、总结

联合体是C语言中一种独特而强大的数据结构,它通过内存共享实现了空间的高效利用:

  • 核心优势:节省内存,适合同一数据的多种表示形式
  • 使用要点:同一时间只有一个成员有效,最后赋值的成员覆盖之前的值
  • 应用场景:协议解析、类型转换、内存敏感环境
  • 最佳实践:配合标签记录当前有效成员,避免未定义行为

掌握了联合体,你就能更灵活地设计数据结构,在内存效率和代码清晰度之间找到最佳平衡点。现在,尝试在你的项目中寻找适合使用联合体的场景吧!


C语言联合体详解:共用内存空间的灵活数据结构
https://www.edenzeng.online/2015/11/02/0.技术栈/01.开发语言/01.C语言/13-联合体详解/
作者
Edenzeng
发布于
2015年11月2日
许可协议