Skip to content
On this page

结构体字节对齐原则


标签:clang/basic  

一般的情况

结构体第一个变量的地址等于结构体变量的地址, 结构体变量的地址是连续的 总字节的大小是成员字节大小的总和, 可以用 sizeof 运算符 ,

但是需要遵循 ==字节对齐原则 ==:

C语言结构体的字节对齐原则是为了保证结构体变量在内存中的存储方式是连续的,且访问结构体变量的每个成员都是有效的。

具体的字节对齐原则如下:

  1. 对于结构体中的每个成员, 其大小都必须是该成员数据类型大小的整数倍。
  2. 结构体变量的起始地址必须是成员大小的整数倍。
  3. 结构体中相邻成员的地址之间的偏移量必须是它们类型大小的整数倍。
  4. 结构体的总大小必须是结构体中最大成员大小的整数倍。

总大小:结构体中最大对齐值是操作系统默认对齐值和结构体中最大对齐值的最小值的倍数(针对第四条)。可以通过#pargma pack()修改系统默认对齐值,括号里可以放2^n次数字,比如1,2,4,8....

c
typedef struct
{
    char a;     //1
    int b;      //4
    int d;      //4
} __attribute__((packed))  B; // 取消结构体对象,Linux 特有

为了满足这些规则,编译器在分配结构体变量的内存空间时会进行字节对齐。对于不同的编译器或不同的编译器选项,字节对齐的规则可能会有所不同。通常,可以通过编译器选项或pragma指令来控制字节对齐方式。

上面第三条有点难以理解, 所以用一个简单的例子解释下:

c
struct myStruct { char c; int i; short s; };

假设上面一个结构体的初始位置为 0x0,

  • 那么 0x0 这个位置存放 1 字节的 char 类型没有问题.
  • 然后 int 成员本来是放在 0x1 这个位置, 但依据原则3, 它需要放在 4n 偏移量的位置上, 所以是放在 0x4 的位置, 它的长度为 4.
  • 它后面的 short 成员, 本来跟着它放在 0x8 的位置, 这刚好符合原则3,
  • 但此时总长度不是 10, 别忘了还有原则4, 结构体大小必须是最大成员大小的整数倍, 但 10 不是 4 的整数倍, 所以还要补上 2. 因此, 总的长度为 字节.

再来练习几个例子 :

c
struct example1 { double c; int a; int *p; char b; };
  • double c : 0 ~ 7
  • int a : 可以从 8 开始, 8 ~ 11
  • int *p : 必须从 16 开始 ( 规则3 ), 16 ~ 23
  • char b : 可以从 24 开始, 1 字节
    • 此时千万不要把偏移量当做总长度, 长度目前是 0~24 共 25
    • 为了补齐 8 的倍数, 所以长度还要填充到 32
c
struct example2 { char a; int b; float c; char *p; };
  • char a : 位置 0
  • int b: 必须从 4 开始 4 ~ 7
  • float c: 可以从 8 开始, 8 ~ 11
  • char *p: 必须从 16 开始, 16 ~ 23
  • 总长度 0 ~ 23 共 24 字节

含数组的情况

c
struct example3 { int a; char b[5]; };

数组元素在结构体内的时候, 数组每个元素可以看作单独的结构体元素

|150

嵌套其他结构体的情况

c
struct example1 { double c; int a; int *p; char b; };
struct example4 { int d; struct example1 e; char f; };
struct example5 { int d; struct example1 e; };

example1 的大小我们上面 #一般的情况 中分析过了, 是 32.

那么在 example4 中, 它视作一个元素, 需要在 32 的倍数的地方对齐吗?

运行结果为 48, 那么显然不是 😱. 不然结果也必须是 32 倍数对吧.

我们同样计算一下 example5 的结果是 40, 显然大小遵循 8 的倍数.

而只有 example1 中最大的元素才是 8, 所以我们有理由猜测计算 example4 的时候, 可以把 example1 的元素当做 example 4 的元素进行计算.

按照这种猜测, 分析一下:

  • 首先 int d 占用 4 字节, 0 ~ 3
  • 然后 double c 占用 8 字节, 8 ~ 15
  • 然后 int a 占用 4 字节, 16 ~ 19
  • 然后 int *p 占用 8 字节, 24 ~ 31
  • 然后 char b 占用 1 字节, 32
  • 此时对于 example5 来说, 已经长度是 33 了, 补齐对 8 倍数的, 为 40.
  • 但是对于 example4 来说, 这个 char f 到底放在 33 的位置上还是 41 的位置?
  • 从运行结果看, 可以发现是 41 的位置, 然后补齐 8 倍, 总共 48 字节
  • 也就是说, 对于嵌套的结构体, 先满足子结构体内部对齐, 然后再继续分配外部

Last updated: