结构体进阶:嵌套、传参、内存对齐与位段
结构体这东西,说白了就是C语言里用来“打包数据”的利器。基础用法大家都会,但真正到了项目里,怎么用好它、怎么避免踩坑,这里头门道不少。今天我就把结构体进阶的几个核心知识点掰开揉碎讲清楚。
一、结构体的嵌套
结构体里面再放结构体,这在实际项目中太常见了。比如描述一个学生,除了姓名学号,还有出生日期——日期本身又是一个结构体。
struct Date {
int year;
int month;
int day;
};
struct Student {
char name[32];
int id;
struct Date birthday; // 嵌套结构体
};
访问的时候一层层点下去就行:stu.birthday.year。我见过有人写代码时忘了中间那层,直接写stu.year,编译器报错还一脸懵——嗯,这种错误我也犯过。
struct Student stu = {"张三", 1001, {2000, 5, 15}};,这样代码可读性更好。
二、结构体作为函数参数
结构体传参有两种方式:传值 和 传指针。我建议你记住一句话:小结构传值,大结构传指针。
传值会把整个结构体拷贝一份,如果结构体很大(比如几百字节),每次调用函数都要拷贝,性能就下来了。我在做嵌入式项目时,一个结构体里放了传感器校准参数,足足128字节,传值调用一次就要拷贝128字节,循环里调个几百次,CPU时间全浪费在拷贝上了。
// 传值——适合小结构体
void printStudent(struct Student s) {
printf("Name: %s\n", s.name);
}
// 传指针——适合大结构体
void updateStudent(struct Student *ps) {
ps->id = 1002; // 用->访问成员
}
传指针还有个好处:可以在函数内部修改原结构体的内容。如果你不想被修改,加个const修饰就行:void show(const struct Student *ps)。
三、结构体的内存对齐
这个问题面试常考,项目里也经常遇到。说白了就是:编译器会在结构体成员之间插入填充字节,让每个成员的地址对齐到它自身大小的整数倍。
为什么会这样?因为CPU访问对齐的数据更快。有些ARM芯片甚至不支持非对齐访问,一访问就异常。
struct Example1 {
char a; // 1字节
int b; // 4字节
char c; // 1字节
};
你猜这个结构体多大?不是1+4+1=6字节,而是12字节!
| 成员 | 偏移量 | 大小 | 填充 |
|---|---|---|---|
| char a | 0 | 1 | 0 |
| int b | 4 | 4 | 3字节(对齐到4) |
| char c | 8 | 1 | 0 |
| 填充 | 9-11 | 3 | 结构体总大小对齐到最大成员 |
优化方法很简单:把大的成员往前放。比如改成 int b; char a; char c;,大小就变成8字节了。
四、位段
位段(bit-field)是C语言里一个比较“嵌入式”的特性。它允许你精确控制每个成员占多少位,非常适合用来描述硬件寄存器或者协议帧。
struct Flags {
unsigned int ready : 1; // 只占1位
unsigned int error : 1; // 只占1位
unsigned int mode : 2; // 占2位,可表示0-3
unsigned int count : 4; // 占4位,可表示0-15
};
这个结构体总共只占8位(1字节),而不是4个int的16字节。我在做CAN总线协议解析时,就用位段来解析报文ID和数据域,代码写起来非常直观。
另外,位段的成员类型必须是int、unsigned int或signed int(C99之后也支持_Bool)。不能取位段成员的地址,因为位段可能不是从字节边界开始的。
知识体系总览
下面这张图把结构体进阶的四个核心知识点串起来了,方便你整体把握:
结构体嵌套让数据组织更自然,传参方式影响性能,内存对齐影响空间和访问效率,位段则给了你比特级的控制力。这四个知识点在实际项目中环环相扣,你想想看,一个网络协议栈里,报文结构体既要嵌套多层头部,又要考虑对齐,还可能用到位段解析标志位——全用上了。
好了,这一章就到这里。记住我一句话:结构体用得好,代码质量差不了。
公众号:蓝海资料掘金营,微信deep3321