1. 结构体与共用体的本质区别
在C语言中,结构体(struct)和共用体(union)是两种看似相似实则完全不同的复合数据类型。它们的核心差异体现在内存分配方式上:
结构体的每个成员都拥有独立的内存空间,而共用体的所有成员共享同一块内存区域。这意味着:
- 结构体的大小等于所有成员大小之和(考虑对齐)
- 共用体的大小等于最大成员的大小
c复制// 结构体示例
struct Student {
char name[20]; // 20字节
int age; // 4字节
float score; // 4字节
}; // 总大小约28字节(考虑对齐)
// 共用体示例
union Data {
int i; // 4字节
float f; // 4字节
char str[20]; // 20字节
}; // 总大小20字节
关键提示:在嵌入式系统中,共用体常用于寄存器映射或协议解析,可以节省宝贵的内存资源。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 结构体的高级用法
2.1 位域(Bit Field)技术
结构体支持位级精确控制,这在硬件编程中尤为重要:
c复制struct {
unsigned int flag1 : 1; // 1位
unsigned int flag2 : 3; // 3位
unsigned int type : 4; // 4位
} status;
这种技术常用于:
- 嵌入式设备的状态寄存器配置
- 网络协议头部的标志位定义
- 内存敏感型应用的紧凑数据存储
2.2 柔性数组(Flexible Array)
C99标准引入的灵活数组成员特性:
c复制struct flex_array {
int length;
double data[]; // 柔性数组成员
};
使用场景:
- 动态创建变长数据结构
- 网络数据包解析
- 高效内存管理
3. 共用体的实战技巧
3.1 类型转换的优雅实现
共用体可以实现安全的类型转换而不需要指针强制转换:
c复制union Converter {
float f;
unsigned int i;
} conv;
conv.f = 3.14;
printf("IEEE754表示: 0x%08x", conv.i);
这种方法避免了指针类型转换可能导致的未定义行为。
3.2 协议解析中的应用
在网络编程中,共用体可以优雅地处理不同格式的数据包:
c复制union Packet {
struct {
uint16_t type;
uint16_t length;
uint8_t data[256];
} standard;
struct {
uint32_t magic;
uint8_t command;
} control;
};
4. 内存对齐的深入理解
4.1 对齐规则详解
处理器访问未对齐数据可能导致性能下降甚至硬件异常。结构体的对齐遵循以下原则:
- 成员对齐:每个成员相对于结构体首地址的偏移量必须是其类型大小的整数倍
- 总体对齐:结构体总大小必须是最大成员大小的整数倍
c复制struct Alignment {
char a; // 1字节
// 3字节填充
int b; // 4字节
short c; // 2字节
// 2字节填充
}; // 总大小12字节
4.2 手动优化对齐
通过成员排序优化可以减小结构体大小:
c复制// 优化前:12字节
struct Bad {
char a;
int b;
char c;
};
// 优化后:8字节
struct Good {
int b;
char a;
char c;
};
经验法则:按成员大小降序排列可以最小化填充字节。
5. 结构体与共用体的组合应用
5.1 变体记录(Variant Record)
结合枚举类型创建灵活的数据结构:
c复制enum DataType { INT, FLOAT, STRING };
struct Variant {
enum DataType type;
union {
int i;
float f;
char s[20];
} value;
};
5.2 硬件寄存器映射
在嵌入式开发中模拟硬件寄存器组:
c复制typedef union {
struct {
uint32_t enable : 1;
uint32_t mode : 3;
uint32_t reserved : 28;
} bits;
uint32_t word;
} ControlRegister;
6. 常见陷阱与调试技巧
6.1 浅拷贝问题
结构体直接赋值是浅拷贝,可能引发内存问题:
c复制struct Person {
char *name;
int age;
};
struct Person p1 = { strdup("Alice"), 25 };
struct Person p2 = p1; // 危险!共享name指针
// 正确做法:实现深拷贝函数
void person_copy(struct Person *dest, const struct Person *src) {
dest->name = strdup(src->name);
dest->age = src->age;
}
6.2 字节序问题
在不同架构间传输结构体数据时:
c复制#pragma pack(push, 1) // 取消对齐
struct NetworkPacket {
uint16_t length;
uint32_t checksum;
};
#pragma pack(pop) // 恢复默认对齐
// 发送前必须转换字节序
packet.length = htons(packet.length);
packet.checksum = htonl(packet.checksum);
7. 现代C语言的新特性
7.1 匿名结构体/共用体
C11标准引入的语法糖:
c复制struct Device {
int id;
union { // 匿名共用体
struct {
float x, y, z;
} accel;
struct {
int rpm;
float temp;
} motor;
};
};
// 访问更简洁
Device d;
d.accel.x = 1.0; // 无需中间共用体名
7.2 复合字面量
C99引入的便捷初始化方式:
c复制// 传统方式
struct Point p1;
p1.x = 10;
p1.y = 20;
// 复合字面量
struct Point p2 = (struct Point){ .x = 10, .y = 20 };
8. 性能优化实践
8.1 缓存友好设计
优化结构体布局提升缓存命中率:
c复制// 优化前:可能引起缓存行频繁切换
struct BadLayout {
int hot_data; // 频繁访问
char padding[60];
int cold_data; // 很少访问
};
// 优化后:热数据集中存放
struct GoodLayout {
int hot_data1;
int hot_data2;
// ...
int cold_data;
};
8.2 预分配对象池
避免频繁内存分配:
c复制#define POOL_SIZE 100
struct ObjectPool {
struct Object items[POOL_SIZE];
int free_list[POOL_SIZE];
int free_count;
};
void init_pool(struct ObjectPool *pool) {
for (int i = 0; i < POOL_SIZE; i++) {
pool->free_list[i] = i;
}
pool->free_count = POOL_SIZE;
}
在实际项目中,结构体和共用体的合理使用可以显著提升代码的可读性和性能。特别是在资源受限的嵌入式环境中,对内存布局的精确控制往往能带来意想不到的效果。我个人的经验是,在协议栈开发和硬件交互层,这些技术几乎无处不在。
