结构体与联合体优化:内存对齐、位域使用、联合体的妙用
结构体和联合体,是C语言里最常用的复合数据类型。但说实话,很多人用了好几年,还是没搞明白怎么用才高效。我见过不少项目,结构体定义得随心所欲,结果内存浪费严重,性能也受影响。今天咱们就聊聊怎么把这些东西用好。
一、内存对齐:看不见的浪费
先问个问题:下面这个结构体占多少字节?
struct Example {
char a; // 1字节
int b; // 4字节
char c; // 1字节
};
很多人会算:1 + 4 + 1 = 6字节。但实际呢?在我的机器上,sizeof(struct Example) 返回的是12字节。为什么?
这就是内存对齐在作怪。CPU读取内存时,不是按字节读的,而是按字(4字节或8字节)读的。如果int变量没有对齐到4字节边界,CPU就得读两次,再拼起来。这太慢了。
对齐规则其实很简单:
- 每个成员的对齐要求,等于它自身的大小(比如int是4字节对齐)
- 结构体的总大小,必须是最大成员对齐值的整数倍
- 编译器会在成员之间和末尾插入填充字节
我在项目中遇到过这样一个坑:一个网络协议的结构体,因为没注意对齐,在x86上跑得好好的,换到ARM上就崩了。查了两天才发现是对齐问题。嗯,从那以后我写结构体都会加一句:
#pragma pack(push, 1) // 取消对齐
struct PacketHeader {
uint8_t type;
uint32_t length;
uint16_t checksum;
};
#pragma pack(pop)
二、优化内存布局:重新排列成员
其实有个更优雅的办法——调整成员顺序。把大的成员放前面,小的放后面,就能减少填充。
// 不好的布局:12字节
struct Bad {
char a;
int b;
char c;
};
// 好的布局:8字节
struct Good {
int b; // 4字节
char a; // 1字节
char c; // 1字节
// 填充2字节
};
你想想看,只是换了个顺序,就省了4个字节。如果结构体数组有1000个元素,那就是4KB的差距。嵌入式系统里,这点空间可能就决定了你的程序能不能跑起来。
三、位域:精确控制比特
位域这东西,说白了就是让你能操作一个字节里的某几个比特。我最早接触它是在做通信协议解析的时候——一个字节里塞了好几个标志位,不用位域就得写一堆移位和掩码操作。
struct Flags {
unsigned int ready : 1; // 1比特
unsigned int error : 1; // 1比特
unsigned int mode : 2; // 2比特
unsigned int count : 4; // 4比特
};
这个结构体只占1个字节,却存了4个字段。如果用普通成员,至少得4个字节。
位域的使用要点:
- 位域的类型必须是int、unsigned int或signed int
- 位域不能取地址(不能用&操作符)
- 位域数组不存在
- 不同编译器的位域布局可能不同——这是个大坑
四、联合体:同一块内存的不同解读
联合体是我最喜欢的C语言特性之一。它让同一块内存地址,在不同时刻可以当作不同类型来用。说白了,就是内存复用。
union Data {
int i;
float f;
char str[4];
};
这个联合体只占4个字节(最大成员的大小)。你可以存一个int,然后读成一个float——虽然结果可能很奇怪,但语法上是合法的。
联合体的经典用法:
- 协议解析:一个数据包可能有多种类型,用联合体可以共用解析缓冲区
- 类型转换:用联合体实现reinterpret_cast的效果(C语言没有这个关键字)
- 节省内存:多个字段不会同时使用,共用一块内存
// 协议解析的典型用法
struct Packet {
uint8_t type;
union {
struct { uint32_t addr; } connect;
struct { uint8_t data[64]; } send;
struct { uint8_t code; } error;
} payload;
};
我在做物联网网关项目时,就用这种结构处理了十几种不同类型的消息。每个包只占固定大小的内存,解析起来也特别清晰。
五、结构体与联合体的组合技巧
把结构体和联合体组合起来,能玩出很多花样。比如实现一个变体类型:
typedef struct {
int type; // 0: int, 1: float, 2: string
union {
int i;
float f;
char* s;
} value;
} Variant;
这种模式在脚本引擎、配置系统里特别常见。一个变量可以存不同类型的数据,运行时根据type字段决定怎么解读value。
核心思路:结构体负责组织数据,联合体负责复用内存。两者结合,既能保持代码清晰,又能节省空间。
六、知识体系总览
下面这张图总结了结构体与联合体优化的核心知识点:
七、实战建议
说了这么多,总结几条我个人的实战经验:
- 先想清楚数据的使用场景——哪些字段经常一起访问?哪些字段互斥?这决定了你用结构体还是联合体
- 能用普通成员就别用位域——位域的性能和可移植性都不如普通成员
- 联合体配合枚举类型使用——用枚举标记当前联合体里存的是什么类型,避免读错
- 写代码时就把对齐考虑进去——别等出问题了再回来改,那时候改起来成本高得多
嗯,结构体和联合体优化,说白了就是理解内存布局,然后根据实际需求做取舍。没有银弹,但掌握了这些技巧,你的C代码至少能省30%的内存,性能也能提升不少。
公众号:蓝海资料掘金营,微信deep3321