结构体的对齐规则:
1.结构体的第一个成员对齐到和结构体变量起始位置偏移量为0的地址处
2.其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
对齐数 = 编译器默认的一个对齐数与该成员变量大小的较小值。
VS 中默认的值为 8
Linux中 gcc 没有默认对齐数,对齐数就是成员自身的大小
3. 结构体总大小为最大对齐数(结构体中每个成员变量都有一个对齐数,所有对齐数中最大的)的 整数倍。
4. 如果嵌套了结构体的情况,嵌套的结构体成员对齐到自己的成员中最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体中成员的对齐数)的整数倍。
//练习1 struct S1 { char c1; int i; char c2; }; printf("%d\n", sizeof(struct S1));//12
以下以VS分析:
- char c1:占用1个字节 - 从偏移量为0的地址处开始对齐
- 填充:3个字节,以确保int i从4的倍数地址开始 - 确保下一个地址的偏移量为4的倍数
- int i:占用4个字节 - 编译器默认对齐数为8,int类型的大小为4个字节 ,因此该对齐数采用较小的4
- int i 对齐完后,偏移量为7,下一个偏移量为8,是char类型对齐数的整数倍
- char c2:占用1个字节 -偏移量为8
- 到这里结构体占用了9个字节
- 填充:1个字节,结构体的总大小是最大对齐数的倍数,在这里,最大对齐数为4。结构体目前大小为9,不是4的倍数,填充3个字节,大小为12,是4的整数倍。
- 所以,struct S1的总大小是1(c1)+ 3(填充)+ 4(i)+ 1(c2)+ 3(填充)= 12字节。
下面给出2个练习,大家动手算算吧。
//练习2 struct S2 { char c1; char c2; int i; }; printf("%d\n", sizeof(struct S2));//8 //练习3 struct S3 { double d; char c; int i; }; printf("%d\n", sizeof(struct S3));//16
//练习4-结构体嵌套问题 struct S4 { char c1; struct S3 s3; double d; }; printf("%d\n", sizeof(struct S4));//32
- char c1:占用1个字节 - 从偏移量为0的地址处开始对齐
- struct S3的最大对齐数为8,因此s3应该从偏移量为8的位置开始对齐
- 填充:7个字节 -- 确保下一个地址的偏移量为8的倍数
- struct S3 s3:占用16个字节
- struct S3 s3对齐完后,偏移量为23,下一个偏移量为24,是doubler类型对齐数的整数倍
- double:占用8个字节 -偏移量为31
- 到这里结构体占用了32字节,结构体的总大小是最大对齐数(含嵌套结构体成员的对齐数)的倍数,在这里,最大对齐数为8。结构体目前大小为32,是8的倍数
- 所以,struct S4的总大小是1(c1)+ 7(填充)+ 16(s3)+ 8(d)= 32字节。
节省空间
在设计结构体的时候,我们既要满足对齐,又要节省空间,如何做到:
让占用空间小的成员尽量集中在一起
//例如: struct S1 { char c1; int i; char c2; }; struct S2 { char c1; char c2; int i; };
修改默认对齐数
#pragma 这个预处理指令,可以改变编译器的默认对齐数。
#include <stdio.h> #pragma pack(1)//设置默认对⻬数为1 struct S { char c1; int i; char c2; }; #pragma pack()//取消设置的对⻬数,还原为默认 int main() { //输出的结果是什么? printf("%d\n", sizeof(struct S)); return 0; }