10 结构体对齐与填充

1. 为什么 sizeof(struct) 大于成员之和

结构体成员可能因对齐要求而插入成员间填充;结构体末尾也可能补齐,以保证结构体数组的每个元素正确对齐。具体布局由平台 ABI、编译器及相关设置决定。

假设 char 为 1 字节,int 为 4 字节且要求 4 字节对齐:

struct A {
    char a;  // 偏移 0
    int  b;  // 偏移 4(前有 3 字节填充)
    char c;  // 偏移 8(末尾再填充 3 字节)
};           // sizeof(struct A) == 12

struct B {
    char a;  // 偏移 0
    char c;  // 偏移 1
    int  b;  // 偏移 4(前有 2 字节填充)
};           // sizeof(struct B) == 8

同样的成员,顺序不同,结构体大小可能不同。

2. sizeof 与 offsetof

#include <stddef.h>

sizeof(struct A);        // 整个结构体的字节数:12
offsetof(struct A, b);   // b 距离结构体起始处的字节偏移:4
offsetof(struct A, c);   // 8

offsetof 用于检查成员偏移;不要把它误认为成员大小或绝对地址。

3. packed 的作用与风险

以下是 GCC/Clang 的编译器扩展写法,不是标准 C 关键字:

struct __attribute__((packed)) Packet {
    char type;  // 偏移 0
    int value;  // 偏移 1
};              // 假设 int 为 4 字节,通常占 5 字节
  • packed 减少或消除通常的对齐填充;不会缩小 int 自身的大小。
  • value 可能处于未对齐地址;直接访问是否安全及效率如何依赖目标架构和编译器。
  • 蓝牙/TWS 二进制协议解析:packed 不负责处理字节序、长度校验或字段合法性;不要未经检查就把收到的数据缓冲区强制转换为结构体指针使用。按协议逐字段读取,或在验证长度和布局后谨慎使用 memcpy,通常更可控。

一句话记忆:对齐决定成员放在哪里;填充影响结构体占多大;packed 改变布局,但不自动保证协议解析安全。