如果你在C语言项目中遇到过这样的场景:需要用一个变量存储不同类型的数据,但又不想浪费内存空间;或者需要处理网络协议包、硬件寄存器等需要按不同方式解释同一块内存的数据结构——那么联合体(union)就是你必须要掌握的关键技术。
很多C语言初学者对联合体的理解停留在“多个成员共用内存”的表面概念,但实际上,联合体真正的价值在于它提供了一种类型安全的内存复用机制。与结构体为每个成员分配独立空间不同,联合体的所有成员共享同一块内存区域,这意味着同一时刻只能有一个成员有效。这种特性让联合体在嵌入式系统、协议解析、内存优化等场景中成为不可替代的工具。
本文将深入解析C语言联合体的核心原理、使用场景和实际应用。不同于简单的语法介绍,我会重点讲解:
- 联合体与结构体的本质区别及其内存布局
- 联合体在协议解析中的实际应用案例
- 通过联合体实现数据转换的技巧
- 联合体使用中的常见陷阱与最佳实践
读完本文,你将不仅理解联合体的语法,更能掌握在实际项目中正确、安全使用联合体的能力。
1. 联合体要解决的核心问题是什么?
在深入语法细节之前,我们先要明白:为什么C语言需要联合体?它解决了哪些结构体无法解决的问题?
1.1 内存效率问题
假设你正在开发一个嵌入式设备的数据采集系统,需要存储不同类型的传感器数据:
// 使用结构体的方式 struct SensorData { int type; // 传感器类型 float temperature; // 温度值 int pressure; // 压力值 char status[20]; // 状态信息 };在这个结构体中,无论实际存储的是什么类型的数据,每个SensorData实例都会占用sizeof(int) + sizeof(float) + sizeof(int) + 20字节的内存。如果系统中有成千上万个这样的数据,内存浪费是惊人的。
而联合体的核心价值就在于:同一时刻只需要存储一种类型的数据,为什么要把所有可能类型的空间都分配出来?
1.2 数据解释的灵活性
另一个典型场景是协议解析。比如一个网络数据包,前4个字节可能被解释为:
- 一个32位整数
- 两个16位整数
- 四个8位字符
- 一个32位浮点数
如果没有联合体,你需要通过指针转换和位操作来实现这种多解释方式,代码会变得复杂且容易出错。
1.3 联合体的本质
联合体本质上是一种类型安全的共用内存机制。它允许你在同一块内存区域中存储不同类型的数据,但编译器会帮你进行类型检查。这与直接使用void指针进行强制类型转换有本质区别:
- 类型安全:编译器知道所有可能的类型
- 内存对齐:编译器会处理对齐问题
- 可读性:代码意图更清晰
2. 联合体的基础语法与内存布局
2.1 联合体的定义
联合体的定义语法与结构体相似:
union Data { int i; float f; char str[20]; };这个定义创建了一个新的类型union Data,它包含三个成员:i、f、str。但关键点是:这三个成员共享同一块内存空间。
2.2 内存大小计算
联合体的大小由其最大成员决定,并且要考虑内存对齐:
#include <stdio.h> union Example { int a; // 通常4字节 double b; // 通常8字节 char c[10]; // 10字节 }; int main() { printf("Size of union Example: %lu bytes\n", sizeof(union Example)); // 输出可能是16字节(考虑对齐) return 0; }运行这个程序,你会发现联合体的大小不是简单的10字节,而是16字节(在大多数64位系统上)。这是因为编译器会按照最大对齐要求来分配空间。
2.3 联合体与结构体的内存对比
让我们通过一个具体的例子来理解两者的区别:
#include <stdio.h> // 结构体:每个成员有独立空间 struct StructData { int type; float value; char name[10]; }; // 联合体:所有成员共享空间 union UnionData { int int_value; float float_value; char string_value[10]; }; int main() { printf("Size of struct StructData: %lu bytes\n", sizeof(struct StructData)); printf("Size of union UnionData: %lu bytes\n", sizeof(union UnionData)); // 典型输出: // Size of struct StructData: 20 bytes // Size of union UnionData: 12 bytes // (具体值取决于系统和编译器对齐规则) return 0; }从内存角度看:
- 结构体:
type、value、name各有自己的内存区域 - 联合体:
int_value、float_value、string_value共享同一块内存
3. 联合体的基本使用与初始化
3.1 声明和初始化联合体变量
#include <stdio.h> union Data { int i; float f; char str[20]; }; int main() { // 方式1:声明后分别赋值 union Data data1; data1.i = 10; printf("data1.i = %d\n", data1.i); // 注意:给一个成员赋值后,其他成员的值是未定义的 printf("data1.f = %f (未定义值)\n", data1.f); // 方式2:声明时初始化第一个成员 union Data data2 = {25}; printf("data2.i = %d\n", data2.i); // 方式3:使用指定初始化器(C99及以上) union Data data3 = {.f = 3.14}; printf("data3.f = %.2f\n", data3.f); // 方式4:使用另一个联合体初始化 union Data data4 = data3; printf("data4.f = %.2f\n", data4.f); return 0; }3.2 访问联合体成员
访问联合体成员使用点运算符(.),与结构体相同:
union Data data; data.i = 100; // 存储整数 printf("%d\n", data.i); data.f = 3.14; // 现在存储浮点数,之前的整数值被覆盖 printf("%f\n", data.f); // 注意:此时data.i的值是未定义的,因为内存已被重新解释3.3 联合体指针
联合体指针的使用也与结构体类似:
union Data data; union Data *ptr = &data; ptr->i = 100; // 通过指针访问成员 printf("%d\n", ptr->i); (*ptr).f = 2.718; // 另一种访问方式 printf("%f\n", ptr->f);4. 联合体的典型应用场景
4.1 场景一:协议数据解析
这是联合体最经典的应用场景。假设你正在处理一个网络协议,其中数据包的前4字节可以按不同方式解释:
#include <stdio.h> #include <stdint.h> // 用于固定宽度整数类型 // 定义协议数据单元 union ProtocolData { uint32_t raw_data; // 原始32位数据 struct { uint16_t header; // 高16位:协议头 uint16_t payload; // 低16位:有效载荷 } parts; struct { uint8_t byte0; // 字节0 uint8_t byte1; // 字节1 uint8_t byte2; // 字节2 uint8_t byte3; // 字节3 } bytes; float float_value; // 解释为浮点数 }; void parse_protocol_data(uint32_t network_data) { union ProtocolData pdu; pdu.raw_data = network_data; printf("原始数据: 0x%08X\n", pdu.raw_data); printf("作为两个16位整数: 0x%04X, 0x%04X\n", pdu.parts.header, pdu.parts.payload); printf("作为四个字节: 0x%02X, 0x%02X, 0x%02X, 0x%02X\n", pdu.bytes.byte0, pdu.bytes.byte1, pdu.bytes.byte2, pdu.bytes.byte3); printf("作为浮点数: %f\n", pdu.float_value); } int main() { // 测试不同的数据解释 parse_protocol_data(0x40490FDB); // π的浮点数表示 printf("\n"); parse_protocol_data(0x12345678); // 测试数据 return 0; }4.2 场景二:节省内存的变体记录
在资源受限的嵌入式系统中,联合体可以显著节省内存:
#include <stdio.h> #include <string.h> // 传感器类型枚举 typedef enum { SENSOR_TEMPERATURE, SENSOR_PRESSURE, SENSOR_HUMIDITY, SENSOR_STATUS } SensorType; // 使用联合体的传感器数据结构 typedef struct { SensorType type; union { float temperature; // 温度值 int pressure; // 压力值 double humidity; // 湿度值 char status[16]; // 状态信息 } value; } SensorData; void print_sensor_data(const SensorData *data) { printf("传感器类型: "); switch(data->type) { case SENSOR_TEMPERATURE: printf("温度 - 值: %.2f°C\n",>#include <stdio.h> #include <stdlib.h> #include <string.h> // 支持的数据类型 typedef enum { VAR_INT, VAR_FLOAT, VAR_STRING, VAR_BOOL } VarType; // 变体类型定义 typedef struct { VarType type; union { int int_val; float float_val; char* string_val; int bool_val; // 0=false, 1=true } data; } Variant; // 创建变体 Variant create_int(int value) { Variant v; v.type = VAR_INT; v.data.int_val = value; return v; } Variant create_float(float value) { Variant v; v.type = VAR_FLOAT; v.data.float_val = value; return v; } Variant create_string(const char* value) { Variant v; v.type = VAR_STRING; v.data.string_val = malloc(strlen(value) + 1); strcpy(v.data.string_val, value); return v; } // 打印变体 void print_variant(const Variant* v) { switch(v->type) { case VAR_INT: printf("整数: %d\n", v->data.int_val); break; case VAR_FLOAT: printf("浮点数: %.2f\n", v->data.float_val); break; case VAR_STRING: printf("字符串: %s\n", v->data.string_val); break; case VAR_BOOL: printf("布尔值: %s\n", v->data.bool_val ? "true" : "false"); break; } } // 清理资源 void free_variant(Variant* v) { if(v->type == VAR_STRING && v->data.string_val != NULL) { free(v->data.string_val); v->data.string_val = NULL; } } int main() { Variant vars[4]; vars[0] = create_int(42); vars[1] = create_float(3.14159); vars[2] = create_string("Hello, Union!"); vars[3].type = VAR_BOOL; vars[3].data.bool_val = 1; for(int i = 0; i < 4; i++) { print_variant(&vars[i]); } // 清理 for(int i = 0; i < 4; i++) { free_variant(&vars[i]); } return 0; }5. 联合体与结构体的组合使用
在实际项目中,联合体经常与结构体组合使用,形成更复杂的数据结构:
5.1 匿名联合体(C11标准)
C11标准引入了匿名联合体,可以简化代码:
#include <stdio.h> // 传统方式:需要.value. struct DataOld { int type; union { int i; float f; } value; }; // C11匿名联合体:直接访问成员 struct DataNew { int type; union { int i; float f; }; // 匿名联合体 }; int main() { struct DataOld old; old.type = 1; old.value.i = 100; // 需要.value. struct DataNew new; new.type = 1; new.i = 100; // 直接访问 printf("传统方式: type=%d, i=%d\n", old.type, old.value.i); printf("匿名联合体: type=%d, i=%d\n", new.type, new.i); return 0; }5.2 复杂数据结构示例
#include <stdio.h> #include <string.h> // 消息类型枚举 typedef enum { MSG_TEXT, MSG_NUMBER, MSG_COORDINATE } MessageType; // 坐标结构体 typedef struct { double x; double y; } Coordinate; // 消息结构体:包含匿名联合体 typedef struct { MessageType type; char sender[32]; union { char text[256]; // 文本消息 double number; // 数字消息 Coordinate coord; // 坐标消息 }; } Message; void print_message(const Message* msg) { printf("发送者: %s\n", msg->sender); printf("消息类型: "); switch(msg->type) { case MSG_TEXT: printf("文本消息\n"); printf("内容: %s\n", msg->text); break; case MSG_NUMBER: printf("数字消息\n"); printf("数值: %.2f\n", msg->number); break; case MSG_COORDINATE: printf("坐标消息\n"); printf("坐标: (%.2f, %.2f)\n", msg->coord.x, msg->coord.y); break; } printf("---\n"); } int main() { Message messages[3]; // 文本消息 messages[0].type = MSG_TEXT; strcpy(messages[0].sender, "Alice"); strcpy(messages[0].text, "Hello, World!"); // 数字消息 messages[1].type = MSG_NUMBER; strcpy(messages[1].sender, "Bob"); messages[1].number = 3.14159; // 坐标消息 messages[2].type = MSG_COORDINATE; strcpy(messages[2].sender, "Charlie"); messages[2].coord.x = 12.34; messages[2].coord.y = 56.78; // 打印所有消息 for(int i = 0; i < 3; i++) { print_message(&messages[i]); } // 计算内存节省 printf("每个Message的大小: %lu bytes\n", sizeof(Message)); printf("如果不使用联合体,估计需要: %lu bytes\n", sizeof(MessageType) + 32 + 256 + sizeof(double) + sizeof(Coordinate)); return 0; }6. 联合体的高级技巧与注意事项
6.1 字节序(Endianness)问题
在处理网络协议或二进制文件时,字节序是一个重要考虑因素:
#include <stdio.h> #include <stdint.h> // 检测系统字节序 void check_endianness() { union { uint32_t i; uint8_t c[4]; } test = {0x12345678}; printf("数值: 0x%08X\n", test.i); printf("字节表示: 0x%02X 0x%02X 0x%02X 0x%02X\n", test.c[0], test.c[1], test.c[2], test.c[3]); if(test.c[0] == 0x78) { printf("系统是小端序 (Little Endian)\n"); } else if(test.c[0] == 0x12) { printf("系统是大端序 (Big Endian)\n"); } } // 网络字节序(大端序)到主机字节序转换 uint32_t ntohl_manual(uint32_t netlong) { union { uint32_t value; uint8_t bytes[4]; } u; u.value = netlong; // 如果是小端系统,需要转换 #if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ return ((uint32_t)u.bytes[0] << 24) | ((uint32_t)u.bytes[1] << 16) | ((uint32_t)u.bytes[2] << 8) | (uint32_t)u.bytes[3]; #else return u.value; // 大端系统不需要转换 #endif } int main() { check_endianness(); printf("\n"); uint32_t network_value = 0x12345678; uint32_t host_value = ntohl_manual(network_value); printf("网络字节序: 0x%08X\n", network_value); printf("转换为主机字节序: 0x%08X\n", host_value); return 0; }6.2 类型双关(Type Punning)的合法性问题
类型双关指的是通过一种类型写入联合体,然后通过另一种类型读取。这在C语言中是合法的,但在C++中是未定义行为:
#include <stdio.h> #include <stdint.h> // C语言中合法的类型双关 float int_bits_to_float(uint32_t i) { union { uint32_t i; float f; } u; u.i = i; return u.f; } uint32_t float_bits_to_int(float f) { union { uint32_t i; float f; } u; u.f = f; return u.i; } int main() { // 将浮点数的位模式解释为整数 float pi = 3.14159f; uint32_t bits = float_bits_to_int(pi); printf("浮点数: %f\n", pi); printf("位模式: 0x%08X\n", bits); printf("转换回来: %f\n", int_bits_to_float(bits)); // 特殊值测试 float zero = 0.0f; float neg_zero = -0.0f; printf("\n+0.0的位模式: 0x%08X\n", float_bits_to_int(zero)); printf("-0.0的位模式: 0x%08X\n", float_bits_to_int(neg_zero)); return 0; }重要提示:在C++中,这种类型双关是未定义行为。如果需要在C++中实现类似功能,应该使用memcpy:
// C++中的安全实现 float int_bits_to_float_cpp(uint32_t i) { float f; memcpy(&f, &i, sizeof(f)); return f; }6.3 联合体中的数组成员
当联合体包含数组成员时,需要注意数组的内存布局:
#include <stdio.h> union ArrayUnion { int numbers[4]; struct { int a, b, c, d; } elements; long long wide[2]; // 假设long long是8字节 }; int main() { union ArrayUnion u; // 通过数组赋值 for(int i = 0; i < 4; i++) { u.numbers[i] = (i + 1) * 10; } printf("通过数组设置值:\n"); for(int i = 0; i < 4; i++) { printf("numbers[%d] = %d\n", i, u.numbers[i]); } printf("\n通过结构体访问:\n"); printf("elements.a = %d\n", u.elements.a); printf("elements.b = %d\n", u.elements.b); printf("elements.c = %d\n", u.elements.c); printf("elements.d = %d\n", u.elements.d); printf("\n作为两个long long整数:\n"); printf("wide[0] = %lld\n", u.wide[0]); printf("wide[1] = %lld\n", u.wide[1]); // 修改结构体成员会影响数组 u.elements.b = 999; printf("\n修改elements.b后:\n"); printf("numbers[1] = %d\n", u.numbers[1]); return 0; }7. 联合体的常见问题与解决方案
7.1 问题一:忘记当前有效的成员
这是使用联合体时最常见的错误:
#include <stdio.h> union Data { int i; float f; char str[20]; }; void problematic_example() { union Data data; data.i = 100; printf("存储整数: %d\n", data.i); // 错误:没有重置联合体,直接读取其他成员 printf("错误地读取浮点数: %f\n", data.f); // 未定义行为! data.f = 3.14; printf("存储浮点数: %f\n", data.f); // 错误:整数值已被覆盖 printf("错误地读取整数: %d\n", data.i); // 无意义的值! } void correct_example() { union Data data; // 方案1:使用标签记录当前有效类型 typedef enum { TYPE_INT, TYPE_FLOAT, TYPE_STRING } DataType; DataType current_type; data.i = 100; current_type = TYPE_INT; // 读取时检查类型 if(current_type == TYPE_INT) { printf("整数: %d\n", data.i); } // 方案2:每次使用前重新初始化 data.f = 3.14; current_type = TYPE_FLOAT; if(current_type == TYPE_FLOAT) { printf("浮点数: %f\n", data.f); } }7.2 问题二:对齐导致的移植性问题
#include <stdio.h> // 这个联合体在不同平台上的大小可能不同 union AlignmentExample { char c; int i; double d; }; int main() { printf("sizeof(char) = %lu\n", sizeof(char)); printf("sizeof(int) = %lu\n", sizeof(int)); printf("sizeof(double) = %lu\n", sizeof(double)); printf("sizeof(union AlignmentExample) = %lu\n", sizeof(union AlignmentExample)); // 查看对齐要求 printf("\n对齐要求:\n"); printf("_Alignof(char) = %lu\n", _Alignof(char)); printf("_Alignof(int) = %lu\n", _Alignof(int)); printf("_Alignof(double) = %lu\n", _Alignof(double)); return 0; }解决方案:
- 使用标准整数类型(如
int32_t、uint64_t) - 在需要跨平台时显式处理字节序
- 使用编译器的打包指令(如
#pragma pack)但要谨慎
7.3 问题三:联合体包含指针时的内存管理
#include <stdio.h> #include <stdlib.h> #include <string.h> union PointerUnion { int *int_ptr; float *float_ptr; char *str_ptr; }; void pointer_union_example() { union PointerUnion pu; // 分配内存 pu.int_ptr = malloc(sizeof(int) * 10); if(pu.int_ptr == NULL) { printf("内存分配失败\n"); return; } // 使用int指针 for(int i = 0; i < 10; i++) { pu.int_ptr[i] = i * 10; } // 危险:通过float指针访问同一内存 printf("通过float指针访问(危险):\n"); for(int i = 0; i < 10; i++) { printf("%f ", pu.float_ptr[i]); // 错误的解释! } printf("\n"); // 正确:只通过分配时使用的指针类型访问 printf("通过int指针访问(正确):\n"); for(int i = 0; i < 10; i++) { printf("%d ", pu.int_ptr[i]); } printf("\n"); // 清理 free(pu.int_ptr); // 注意:pu.float_ptr和pu.str_ptr现在都是悬空指针 pu.int_ptr = NULL; // 最好将所有指针设为NULL pu.float_ptr = NULL; pu.str_ptr = NULL; }8. 联合体的最佳实践
8.1 实践一:总是使用标签记录当前有效类型
#include <stdio.h> #include <string.h> // 最佳实践:联合体+标签 typedef struct { enum { INT, FLOAT, STRING } type; union { int int_value; float float_value; char string_value[64]; } data; } TaggedUnion; void set_int(TaggedUnion *tu, int value) { tu->type = INT; tu->data.int_value = value; } void set_float(TaggedUnion *tu, float value) { tu->type = FLOAT; tu->data.float_value = value; } void set_string(TaggedUnion *tu, const char *value) { tu->type = STRING; strncpy(tu->data.string_value, value, sizeof(tu->data.string_value) - 1); tu->data.string_value[sizeof(tu->data.string_value) - 1] = '\0'; } void print_tagged_union(const TaggedUnion *tu) { switch(tu->type) { case INT: printf("整数: %d\n", tu->data.int_value); break; case FLOAT: printf("浮点数: %.2f\n", tu->data.float_value); break; case STRING: printf("字符串: %s\n", tu->data.string_value); break; default: printf("未知类型\n"); } }8.2 实践二:为联合体提供安全的访问接口
#include <stdio.h> #include <stdbool.h> // 安全的联合体封装 typedef union { int i; float f; } SafeUnion; typedef struct { bool is_int; SafeUnion value; } SafeContainer; SafeContainer make_int(int value) { SafeContainer sc; sc.is_int = true; sc.value.i = value; return sc; } SafeContainer make_float(float value) { SafeContainer sc; sc.is_int = false; sc.value.f = value; return sc; } bool get_int(const SafeContainer *sc, int *out) { if(sc->is_int && out != NULL) { *out = sc->value.i; return true; } return false; } bool get_float(const SafeContainer *sc, float *out) { if(!sc->is_int && out != NULL) { *out = sc->value.f; return true; } return false; }8.3 实践三:在嵌入式系统中的使用规范
// embedded_union.h #ifndef EMBEDDED_UNION_H #define EMBEDDED_UNION_H #include <stdint.h> // 硬件寄存器映射示例 typedef union { uint32_t raw; // 原始寄存器值 struct { uint32_t enable : 1; // 位0:使能位 uint32_t mode : 2; // 位1-2:模式选择 uint32_t speed : 4; // 位3-6:速度设置 uint32_t reserved : 24; // 位7-31:保留位 uint32_t : 0; // 强制对齐到32位边界 } bits; struct { uint8_t byte0; // 低字节 uint8_t byte1; uint8_t byte2; uint8_t byte3; // 高字节 } bytes; } HardwareRegister; // 传感器数据包 typedef struct { uint8_t sensor_id; uint8_t packet_type; union { struct { int16_t temperature; // 温度,单位0.1°C uint16_t humidity; // 湿度,单位0.1% } environmental; struct { uint16_t pressure; // 压力,单位Pa uint16_t altitude; // 海拔,单位米 } atmospheric; uint8_t raw_data[4]; // 原始数据 } payload; uint8_t checksum; } SensorPacket; // 函数声明 void init_hardware_register(HardwareRegister *reg); uint8_t calculate_checksum(const SensorPacket *packet); bool validate_packet(const SensorPacket *packet); #endif // EMBEDDED_UNION_H// embedded_union.c #include "embedded_union.h" #include <string.h> void init_hardware_register(HardwareRegister *reg) { if(reg == NULL) return; reg->raw = 0; // 清零寄存器 // 设置默认值 reg->bits.enable = 1; // 使能设备 reg->bits.mode = 2; // 模式2:高速模式 reg->bits.speed = 0xF; // 最大速度 // 可以通过bytes访问验证 printf("寄存器值: 0x%08X\n", reg->raw); printf("字节表示: 0x%02X 0x%02X 0x%02X 0x%02X\n", reg->bytes.byte0, reg->bytes.byte1, reg->bytes.byte2, reg->bytes.byte3); } uint8_t calculate_checksum(const SensorPacket *packet) { if(packet == NULL) return 0; uint8_t sum = 0; const uint8_t *data = (const uint8_t*)packet; // 计算除checksum字段外的所有字节和 for(size_t i = 0; i < sizeof(SensorPacket) - 1; i++) { sum += data[i]; } return ~sum + 1; // 二进制补码 } bool validate_packet(const SensorPacket *packet) { if(packet == NULL) return false; uint8_t calculated = calculate_checksum(packet); return (calculated == packet->checksum); }9. 联合体在实际项目中的综合应用
9.1 案例:简单的虚拟机实现
#include <stdio.h> #include <stdint.h> #include <stdlib.h> // 虚拟机操作码 typedef enum { OP_PUSH_INT, OP_PUSH_FLOAT, OP_ADD, OP_SUB, OP_MUL, OP_DIV, OP_PRINT, OP_HALT } OpCode; // 虚拟机指令 typedef struct { OpCode opcode; union { int int_operand; float float_operand; } operand; } Instruction; // 虚拟机栈 typedef union { int int_value; float float_value; } StackValue; // 简单的栈虚拟机 typedef struct { StackValue stack[100]; int stack_ptr; Instruction *program; int pc; // 程序计数器 } VirtualMachine; // 执行程序 void execute_program(VirtualMachine *vm, Instruction *program, int length) { vm->program = program; vm->pc = 0; vm->stack_ptr = -1; while(vm->pc < length) { Instruction instr = vm->program[vm->pc++]; switch(instr.opcode) { case OP_PUSH_INT: vm->stack[++vm->stack_ptr].int_value = instr.operand.int_operand; break; case OP_PUSH_FLOAT: vm->stack[++vm->stack_ptr].float_value = instr.operand.float_operand; break; case OP_ADD: if(vm->stack_ptr >= 1) { // 简单示例:假设都是整数 int b = vm->stack[vm->stack_ptr--].int_value; int a = vm->stack[vm->stack_ptr--].int_value; vm->stack[++vm->stack_ptr].int_value = a + b; } break; case OP_PRINT: if(vm->stack_ptr >= 0) { printf("栈顶值: %d\n", vm->stack[vm->stack_ptr].int_value); } break; case OP_HALT: return; default: printf("未知指令\n"); return; } } } int main() { // 创建一个简单的程序:计算 (10 + 20) * 2 Instruction program[] = { {OP_PUSH_INT, .operand.int_operand = 10}, {OP_PUSH_INT, .operand.int_operand = 20}, {OP_ADD, .operand.int_operand = 0}, // operand unused {OP_PUSH_INT, .operand.int_operand = 2}, {OP_MUL, .operand.int_operand = 0}, {OP_PRINT, .operand.int_operand = 0}, {OP_HALT, .operand.int_operand = 0} }; VirtualMachine vm; execute_program(&vm, program, sizeof(program)/sizeof(program[0])); return 0; }9.2 案例:配置系统实现
#include <stdio.h> #include <string.h> #include <stdlib.h> // 配置值类型 typedef enum { CONFIG_INT, CONFIG_FLOAT, CONFIG_STRING, CONFIG_BOOL } ConfigType; // 配置项 typedef struct { char key[64]; ConfigType type; union { int int_value; float float_value; char *string_value; int bool_value; } value; } ConfigItem; // 配置管理器 typedef struct { ConfigItem *items; int capacity; int count; } ConfigManager; // 初始化配置管理器 ConfigManager* config_create(int initial_capacity) { ConfigManager *cm = malloc(sizeof(ConfigManager)); if(cm == NULL) return NULL; cm->items = malloc(sizeof(ConfigItem) * initial_capacity); if(cm->items == NULL) { free(cm); return NULL; } cm->capacity = initial_capacity; cm->count = 0; return cm; } // 设置配置值 int config_set_int(ConfigManager *cm, const char *key, int value) { if(cm == NULL || key == NULL) return 0; // 查找是否已存在 for(int i = 0; i < cm->count; i++) { if(strcmp(cm->items[i].key, key) == 0) { cm->items[i].type = CONFIG_INT; cm->items[i].value.int_value = value; return 1; } } // 添加新项 if(cm->count >= cm->capacity) { // 扩容 int new_capacity = cm->capacity * 2; ConfigItem *new_items = realloc(cm->items, sizeof(ConfigItem) * new_capacity); if(new_items == NULL) return 0; cm->items = new_items; cm->capacity = new_capacity; } ConfigItem *item = &cm->items[cm->count++]; strncpy(item->key, key, sizeof(item->key) - 1); item->key[sizeof(item->key) - 1] = '\0'; item->type = CONFIG_INT; item->value.int_value = value; return 1; } // 获取配置值(带默认值) int config_get_int(const ConfigManager *cm, const char *key, int default_value) { if(cm == NULL || key == NULL) return default_value; for(int i = 0; i < cm->count; i++) { if(strcmp(cm->items[i].key, key) == 0 && cm->items[i].type == CONFIG_INT) { return cm->items[i].value.int_value; } } return default_value; } // 打印所有配置 void config_print_all(const ConfigManager *cm) { if(cm == NULL) return; printf("配置项 (%d/%d):\n", cm->count, cm->capacity); printf("====================\n"); for(int i = 0; i < cm->count; i++) { printf("%-20s: ", cm->items[i].key); switch(cm->items[i].type) { case CONFIG_INT: printf("%d (int)\n", cm->items[i].value.int_value); break; case CONFIG_FLOAT: printf("%.2f (float)\n", cm->items[i].value.float_value); break; case CONFIG_STRING: printf("%s (string)\n", cm->items[i].value.string_value); break; case CONFIG_BOOL: printf("%s (bool)\n", cm->items[i].value.bool_value ? "true" : "false"); break; } } } // 清理资源 void config_destroy(ConfigManager *cm) { if(cm == NULL) return; // 释放所有字符串内存 for(int i = 0; i < cm->count; i++) { if(cm->items[i].type == CONFIG_STRING && cm->items[i].value.string_value != NULL) { free(cm->items[i].value.string_value); } } free(cm->items); free(cm); } int main() { // 创建配置管理器 ConfigManager *config = config_create(10); if(config == NULL) { printf("创建配置管理器失败\n"); return 1; } // 设置各种类型的配置 config_set_int(config, "max_connections", 100); config_set_int(config, "timeout", 30); // 获取配置值 int max_conn = config_get_int(config, "max_connections", 50); int timeout = config_get_int(config, "timeout", 10); int not_found = config_get_int(config, "not_exist", 999); // 使用默认值 printf("max_connections: %d\n", max_conn); printf("timeout: %d\n", timeout); printf("not_exist: %d (使用默认值)\n", not_found); // 打印所有配置 config_print_all(config); // 清理 config_destroy(config); return 0; }联合体是C语言中一个强大但容易被误解的特性。正确使用联合体可以显著提高内存效率、简化数据结构的复杂性,并在协议解析、嵌入式系统等场景中发挥关键作用。然而,联合体也带来了类型安全的风险,需要开发者始终保持警惕。
关键要点总结:
- 联合体的核心是内存共享,不是类型转换
- 总是使用标签记录当前有效的成员类型
- 注意字节序和对齐问题,特别是在跨平台开发中
- 在C++中使用联合体进行类型双关是未定义行为
- 结合结构体使用可以创建更安全、更易用的数据结构
在实际项目中,联合体最适合的场景包括:协议数据解析、硬件寄存器映射、内存受限环境的数据存储、实现变体类型等。对于大多数应用层开发,如果内存不是主要瓶颈,使用结构体配合指针可能是更安全的选择。