共用体是C语言中一种特殊的数据结构,它与结构体(struct)最大的区别在于内存分配方式。在共用体中,所有成员共享同一块内存空间,这块内存的大小由最大的成员决定。这意味着在任何时刻,共用体只能存储其中一个成员的值。
重要提示:对共用体某个成员的赋值会覆盖其他成员的值,因为它们共享同一内存地址。这是共用体与结构体最本质的区别。
从内存布局来看,假设我们有以下共用体定义:
c复制union Example {
int num;
float f;
char str[4];
};
在32位系统中,这个共用体将占用4字节内存(因为int和float都是4字节,char[4]也是4字节)。三个成员num、f和str都从同一内存地址开始。
在资源受限的嵌入式系统中,共用体可以显著减少内存占用。典型应用场景包括:
c复制// 协议数据包示例
union Packet {
struct {
uint8_t type;
union {
uint32_t ip_addr;
char domain[32];
} address;
} network;
uint8_t raw[33]; // 原始字节流访问
};
实际经验:在STM32等MCU开发中,使用共用体处理传感器数据可以节省30%-50%的RAM使用量。
共用体结合枚举类型可以实现简单的多态特性。这种模式在以下场景特别有用:
扩展原始示例,我们可以实现更完善的类型系统:
c复制typedef enum {
INT,
FLOAT,
STRING,
DOUBLE,
LONG
} VariantType;
typedef struct {
VariantType type;
union {
int i;
float f;
double d;
long l;
char str[1]; // 柔性数组成员
} data;
} Variant;
// 使用示例
Variant create_int(int value) {
Variant v;
v.type = INT;
v.data.i = value;
return v;
}
void print_variant(Variant v) {
switch(v.type) {
case INT: printf("%d", v.data.i); break;
case FLOAT: printf("%f", v.data.f); break;
// 其他类型处理...
}
}
在嵌入式开发中,共用体+位域是访问硬件寄存器的标准做法。典型应用包括:
改进后的寄存器操作示例:
c复制// GPIO寄存器定义
typedef union {
uint32_t reg; // 完整寄存器值
struct {
uint32_t mode0 : 2; // 引脚0模式
uint32_t mode1 : 2; // 引脚1模式
// ...其他引脚
uint32_t reserved : 22; // 保留位
} bits;
} GPIO_Mode_TypeDef;
// 使用示例
void set_gpio_mode(GPIO_Mode_TypeDef *reg, int pin, int mode) {
switch(pin) {
case 0: reg->bits.mode0 = mode; break;
case 1: reg->bits.mode1 = mode; break;
// ...
}
}
开发经验:在STM32 HAL库中,所有外设寄存器都采用这种定义方式。通过共用体可以同时实现位级操作和整体寄存器访问。
共用体非常适合处理网络协议和文件格式解析,特别是当协议中存在变体字段时:
c复制// TCP/IP协议头解析示例
union IPHeader {
uint8_t raw[20];
struct {
uint8_t ver_ihl; // 版本和头部长度
uint8_t tos; // 服务类型
uint16_t total_length;
// ...其他字段
} fields;
};
void process_packet(union IPHeader *header) {
uint8_t version = header->fields.ver_ihl >> 4;
uint8_t ihl = header->fields.ver_ihl & 0x0F;
// 解析其他字段...
}
共用体可以确保数据在不同平台间保持一致的二进制表示:
c复制union FloatInt {
float f;
uint32_t i;
};
float read_float_from_network(uint32_t network_value) {
union FloatInt converter;
converter.i = ntohl(network_value); // 网络字节序转换
return converter.f;
}
结合指针和共用体,可以实现简单的动态类型:
c复制typedef struct {
enum { INT, FLOAT, STRING } type;
union {
int i;
float f;
char *s;
} value;
} DynamicValue;
void print_value(DynamicValue v) {
switch(v.type) {
case INT: printf("%d", v.value.i); break;
case FLOAT: printf("%f", v.value.f); break;
case STRING: printf("%s", v.value.s); break;
}
}
共用体可能遇到的内存对齐陷阱:
c复制union BadExample {
char c;
int i; // 可能导致对齐问题
};
解决方案:
#pragma pack)在不同字节序的平台上,共用体可能产生不同结果:
c复制union EndianTest {
uint32_t num;
uint8_t bytes[4];
};
// 在小端系统和大端系统表现不同
解决方案:
共用体不提供类型安全检查,容易导致错误:
c复制union Data {
int i;
float f;
};
union Data d;
d.i = 10;
float wrong = d.f; // 错误地访问了错误类型
防御性编程建议:
共用体可用于高效的内存池实现:
c复制#define POOL_SIZE 100
typedef union {
struct {
union MemoryBlock *next;
} free;
char data[16]; // 实际数据存储
} MemoryBlock;
MemoryBlock pool[POOL_SIZE];
void init_pool() {
for(int i=0; i<POOL_SIZE-1; i++) {
pool[i].free.next = &pool[i+1];
}
pool[POOL_SIZE-1].free.next = NULL;
}
利用共用体实现数据压缩存储:
c复制union CompressedDate {
uint16_t raw;
struct {
uint8_t day : 5; // 1-31
uint8_t month : 4; // 1-12
uint8_t year : 7; // 0-127 (相对于某个基准年)
} parts;
};
共用体提供了一种无额外开销的类型转换方式:
c复制union Converter {
float f;
uint32_t u;
};
float sqrt_approx(float x) {
union Converter c;
c.f = x;
c.u = 0x5f3759df - (c.u >> 1); // 快速平方根近似算法
return c.f;
}
C11标准引入了匿名共用体,简化了语法:
c复制struct Variant {
enum { INT, FLOAT } type;
union {
int i;
float f;
}; // 匿名共用体
};
// 使用更简洁
struct Variant v;
v.type = INT;
v.i = 10; // 直接访问,不需要中间data成员
现代C代码中常见的模式:
c复制typedef struct {
uint32_t header;
union {
struct {
uint32_t param1;
uint32_t param2;
} cmd1;
struct {
uint16_t paramA;
uint16_t paramB;
} cmd2;
} payload;
} CommandPacket;
C11的_Generic与共用体结合:
c复制#define print_value(x) _Generic((x), \
int: print_int, \
float: print_float \
)(x)
void print_int(int i) { printf("%d", i); }
void print_float(float f) { printf("%f", f); }
union Value {
int i;
float f;
};
// 使用
union Value v;
v.i = 10;
print_value(v.i); // 打印int
v.f = 3.14;
print_value(v.f); // 打印float
在实际工程中,共用体虽然强大但需要谨慎使用。我在开发嵌入式系统时,通常会遵循以下原则: