具体作用:用于计算结构所占内存的大小,格式如下sizeof(struct xxx)
为什么需要结构体内存对齐?
1.平台原因:不是所有平台都可以任意访问地址上的任意数据,某些硬件平台只可以访问某些地址处的特定类型数据
2.性能原因:尽可能在自然边界上对齐,访问未对齐的内存,处理器要访问两次,而对齐的内存访问一次即可
结构体内存对齐规则:
1.第一个成员在结构体变量偏移量为0处的地址
2.第二个成员开始,每个成员都要分别对齐到各自对齐数的整数倍数处
3.结构体总大小:所有成员的对齐数中最大对齐数的整数倍
4.如果结构体中嵌套结构体成员,要将嵌套结构体的成员对齐到自己的最大对齐数的整数倍处
1.第一个成员在结构体变量偏移量为0处的地址
假设将内存可视化,起始地址标为0,那么123依次排列
2.第二个成员开始,每个成员都要分别对齐到各自对齐数的整数倍数处
对齐数:结构体成员自身大小和默认对齐数的较小值
Visual Studio默认对齐数:8
例如:char类型用1与8比较取1
int类型用4与8比较取4
double类型则取8
linux/gcc:没有默认对齐数,对齐数为成员自身大小
3.结构体总大小:所有成员的对齐数中最大对齐数的整数倍
4.如果结构体中嵌套结构体成员,要将嵌套结构体的成员对齐到自己的最大对齐数的整数倍处,结构体的总大小为最大对齐数(包含嵌套结构体的对齐数)的整数倍(结合例4理解)
结合实例具体分析:
例1:
//结构体内存对齐
struct S1
{
char a;
int b;
char c;
};
int main()
{
printf("%d\n", sizeof(struct S1));//12
return 0;
}
分别有abc这三个成员,其所占内存大小分别为1,4,1 但不可将结构体大小简单理解为三者相加
按照步骤:
1.将a排序在0起始位置处。
2.排布b,int类型的对齐数为4,所有会在4的倍数处排布,因此会浪费123这三个字节的大小。
3.c为char类型可以接在b之后。
4.观察所有成员的类型,发现int为最大,所以按照补全的思想,使其结构体总内存大小为所有成员的对齐数中最大对齐数的整数倍,得到结果为12
例2:
//结构体内存对齐
struct S2
{
char a;
char b;
int c;
};
int main()
{
printf("%d\n", sizeof(struct S2));//8
return 0;
}
跟例1 相似,
1.排布好a在0位置处
2.此时b的类型为char,内存对齐数为1,所以可以排布在1的倍数处(任意位置)
3.c为int类型,从4号位置开始排布
4.综合计算,总共占据内存大小为8字节
例3:
//结构体内存对齐
struct S3
{
double a;
char b;
int c;
};
int main()
{
printf("%d\n", sizeof(struct S3));//16
return 0;
}
相同的思路,只不过此时多出了一个double类型:
1.double类型占据内存大小为8字节
2.char类型的b随意排布在8地址处
3.c为int类型,因此按顺序排在下一个4的倍数12处
4.因为最大的数据类型为double,所以结构体内存大小为8的倍数16
例4:
//结构体内存对齐
struct S3
{
double a;
char b;
int c;
};
struct S4
{
char d;
struct S3 s3;
double e;
};
int main()
{
printf("%d\n", sizeof(struct S3));//16
printf("%d\n", sizeof(struct S4));//32
return 0;
}
S4中嵌套了一个结构体S3:
1.先排好char d,
2.嵌套结构体的成员对齐到自己(嵌套结构体成员)的最大对齐数的整数倍处
回顾S3中结构体成员分别为double,char ,int类型,所以对齐数为:8,所以占据8-23号共16字节
3.排布最后一个e,类型为double
4.观察所有的结构体成员(包括嵌套成员的结构体中的成员),最终占据内存大小为最大对齐数的整数倍
修改默认对齐数:
#pragma pack(1)//设置默认对齐数为1
struct S1
{
char a;
int b;
char c;
};
#pragma ()//解除,取消设置的默认对齐数,还原为默认