C语言——结构体类型(二)【内存对齐,数组,指针与函数实战】
1. 结构体内存对齐的底层原理第一次接触结构体内存对齐时我盯着下面这段代码的输出结果发呆了半小时#include stdio.h struct Example { char a; int b; char c; }; int main() { printf(结构体大小%zu\n, sizeof(struct Example)); return 0; }你猜输出是多少在64位系统上这个看似简单的结构体竟然占用了12个字节这就是内存对齐在作怪。现代CPU访问内存时不是逐字节读取的而是按照特定字长如4字节或8字节批量读取。如果数据没有对齐CPU可能需要多次访问内存才能获取完整数据。对齐规则其实很简单每个成员的偏移量必须是其类型大小的整数倍结构体总大小必须是最大成员大小的整数倍用上面的例子分析char a占1字节偏移量0int b需要4字节对齐所以跳过3字节偏移量4char c占1字节偏移量8结构体总大小需要是4的倍数所以补3字节实际项目中我踩过的坑在网络传输协议设计中如果没有显式处理对齐问题不同平台解析时会出现数据错位。解决方法有两种使用编译器指令如#pragma pack(1)取消对齐手动调整成员顺序把char c移到int b前面2. 结构体数组的实战技巧处理学生成绩表时结构体数组比二维数组直观得多。来看个典型场景#define MAX_STUDENTS 50 struct Student { int id; char name[20]; float scores[3]; // 三门课成绩 }; // 初始化示例 struct Student class[MAX_STUDENTS] { {101, 张三, {85.5, 90.0, 78.5}}, {102, 李四, {92.0, 88.5, 95.0}} };几个实用技巧动态数组当数量不确定时用指针malloc更灵活struct Student *dynamicClass malloc(count * sizeof(struct Student));快速清零使用memset初始化memset(class, 0, sizeof(class)); // 全部置零数组排序用qsort按成绩排序int compare(const void *a, const void *b) { struct Student *s1 (struct Student *)a; struct Student *s2 (struct Student *)b; return (s2-scores[0] - s1-scores[0]); // 按第一门课降序 } qsort(class, count, sizeof(struct Student), compare);在嵌入式项目中我常用结构体数组管理设备寄存器表。比如定义GPIO配置struct GpioConfig { uint32_t port; uint8_t pin; uint8_t mode; uint8_t pull; } gpio_table[] { {GPIOA, 3, MODE_OUTPUT, PULL_UP}, {GPIOB, 5, MODE_INPUT, PULL_DOWN} };3. 结构体指针的高阶玩法指针让结构体操作如虎添翼。来看链表实现的经典案例struct Node { int data; struct Node *next; }; // 创建新节点 struct Node* createNode(int value) { struct Node *newNode malloc(sizeof(struct Node)); if(newNode) { newNode-data value; newNode-next NULL; } return newNode; } // 插入节点 void insertAfter(struct Node *prev, int value) { struct Node *newNode createNode(value); newNode-next prev-next; prev-next newNode; }指针使用的三个要点箭头运算符-等价于(*ptr).指针传递比直接传结构体更高效避免拷贝动态分配的内存必须手动释放在图形处理项目中我用结构体指针实现了高效的场景树struct GameObject { float position[3]; struct Model *model; struct GameObject *children; struct GameObject *nextSibling; }; // 遍历渲染 void renderAll(struct GameObject *obj) { while(obj) { drawModel(obj-model, obj-position); if(obj-children) renderAll(obj-children); obj obj-nextSibling; } }4. 结构体与函数的默契配合函数处理结构体有三种传参方式各有利弊传值安全但效率低void printStudent(struct Student s) { printf(ID:%d, Name:%s\n, s.id, s.name); }传指针高效但可能修改原数据void updateScore(struct Student *s, int course, float score) { s-scores[course] score; }返回结构体C99struct Point makePoint(int x, int y) { return (struct Point){x, y}; // 复合字面量 }实际工程经验需要修改原结构体时用指针大结构体优先用指针传递简单结构体可考虑返回值常量指针保护数据void print(const struct Student *s)在通信协议解析中我常用结构体指针作为函数参数#pragma pack(1) // 1字节对齐 struct Packet { uint8_t header; uint16_t length; uint8_t payload[256]; uint8_t checksum; }; int validatePacket(const struct Packet *pkt) { return pkt-checksum calcChecksum(pkt); }