¶原理介绍
计算机系统对基本数据类型的合法地址做出了对齐限制,要求某种类型对象的地址必须是某个值 K 的倍数,K 值通常取 2、4 或者 8,这就叫做数据对齐
数据对齐的作用是,简化形成处理器和内存系统之间接口的硬件设计,减少内存操作,降低内存访问的次数
数据对齐的原则:
1)任何 K 字节的基本对象的地址必须是 K 的倍数,即每种类型的对象都需要满足它的对齐限制
2)为了实现数据对齐,编译器在字段的分配中插入间隙
3)结构本身对它的起始地址也有一些对齐要求
4)编译器需要保证指向结构体的指针也满足对齐要求
5)为了使得结构体数组中的每个元素也满足它的对齐要求,编译器在结构体的末尾也进行一些填充,其填充的字节数由结构体中的元素的最大 K 值决定
在 64 位机器上 K 作如下取值
K | 类型 |
---|---|
1 | char |
2 | short |
4 | int, float |
8 | long, double char* |
¶实例分析
¶例 1
考虑下面的结构体声明
1 | struct S1 { |
假设编译器用最小的 9 字节分配,则字段 i 的偏移为 0,字段 c 的偏移为 4,字段 j 的偏移为 5,这样的分配方式并不满足数据对齐的要求
我们可以在字段 c 和 j 之间插入一个 3 字节的间隙,这样就使得 j 的偏移增加到了 8,整个结构体的大小变为了 12 字节,这样就满足了数据对齐的要求
¶例 2
考虑下面的结构体声明
1 | struct S1 { |
若将这个结构体打包成 9 个字节,只要保证结构的起始地址满足 4 字节对齐要求,我们仍然能够保证满足字段 i 和 j 的对齐要求,但如果用 S2 作如下声明
1 | struct S2 d[4]; |
我们发现,只分配 9 个字节,使得 d[0] 的地址为 $x_d$,d[1]、d[2]、d[3] 的地址分别为 $x_d + 9$、$x_d + 18$、$x_d + 27$,不能满足结构体数组 d 中每个元素的对齐要求,于是编译器会在字段 c 后填充 3 个字节,即为结构体 S2 分配 12 个字节,使得 d[1]、d[2]、d[3] 的地址分别为 $x_d + 12$、$x_d + 24$、$x_d + 36$,那么只要 $x_d$ 是 4 的倍数,所有的对齐限制就都可以满足了
¶练习题
对下面每个结构体声明,确定每个字段的偏移量,结构体总的大小,以及在 x86-64 下它的对齐要求
1 | A. struct P1 { int i; char c; int j; char d; }; |
A.
字段 i 偏移量为 0
字段 c 偏移量为 4
字段 j 偏移量为 8
字段 d 偏移量为 12
结构体总的大小为 16 字节
对齐要求为 4 字节对齐
B.
字段 i 偏移量为 0
字段 c 偏移量为 4
字段 d 偏移量为 5
字段 j 偏移量为 8
结构体总的大小为 16 字节
对齐要求为 8 字节对齐
C.
字段 w 的偏移量为 0
字段 c 的偏移量为 6
结构体总的大小为 10 字节
对齐要求为 2 字节对齐
D.
字段 w 的偏移量为 0
字段 c 的偏移量为 16
结构体总的大小为 40 字节
对齐要求为 8 字节对齐
E.
字段 a 的偏移量为 0
字段 t 的偏移量为 24
结构体总的大小为 40 字节
对齐要求为 8 字节对齐
对于下列结构声明回答后续问题
1 | struct { |
这个结构体中所有的字段的字节偏移量是多少?
这个结构体总的大小是多少?
重新排列这个结构中的字段,以最小化浪费的空间,然后再给出重排过的结构的字节偏移量和总的大小
字段 a 的偏移量为 0
字段 b 的偏移量为 8
字段 c 的偏移量为 16
字段 d 的偏移量为 24
字段 e 的偏移量为 28
字段 f 的偏移量为 32
字段 g 的偏移量为 40
字段 h 的偏移量为 48
填充 4 个字节以满足对 8 字节对齐的要求
结构体总的大小为 56 个字节
重新排列后为
1 | struct { |
字段 a 的偏移量为 0
字段 c 的偏移量为 8
字段 g 的偏移量为 16
字段 e 的偏移量为 24
字段 h 的偏移量为 28
字段 b 的偏移量为 32
字段 d 的偏移量为 34
字段 f 的偏移量为 35
填充 4 个字节以满足对 8 字节对齐的要求
重新排列后结构体总的大小为 40 个字节