一、对齐规则
1、开头:结构体的第一个成员在结构体变量偏移量为0的地址处
列如:
#include<stdio.h>
struct Type
{
int a;
int b;
char c;
}Example;
int main()
{
printf("%p\n%p", &Example, &Example.a);
return 0;
}
运行结果为
说明结构体Example的第一个成员a的起始地址和结构体本身的起始地址是一样的
2、中间:其他成员要对齐到每个成员各自选取的对齐数的整数倍的地址处
什么是对齐数?
对齐数就是相对于结构体起始地址的地址偏移量,那么第一个成员的选取的对齐数就是0
选取的对齐数的取值和该成员的类型和默认对齐数有关:
VS编译器的默认对齐数为8,成员的对齐数和它的类型所占用的字节数相等,比如在32位平台上成员b的类型是int占用4个字节,那么它的对齐数是4;而成员c的类型是char占用1个字节,那么它的对齐数是1
选取对齐数时,将默认对齐数和成员本身的对齐数比较后,选取较小的那个对齐数,比如成员b的对齐数4小于默认对齐数8,所以选取的对齐数为4;成员c的对齐数1小于默认对齐数8,所以选取的对齐数为1
简单来说,成员所占的字节数小于默认对齐数,选择所占字节数的值为对齐数(嵌套结构体的情况除外,下面会讲到)
修改默认对齐数
//修改默认对齐数为4
#pragma pack(4)
内存占用情况
成员a已经占据4个字节,成员b只能从地址偏移量为4的地方开始放置,而4正好是它选取的对齐数4的整数倍,所以成员b放在偏移量为4的地址处。同理,成员a、b连续存放占用了8个字节,所以成员c只能从地址偏移量为8的地方开始放置,而8正好是它选取的对齐数1的整数倍,所以成员c放在偏移量为8的地址处
存放情况如下:
那么有没有成员之间有内存浪费的情况呢,请看下列的例子:
struct Type
{
int a;
char b;
short c;
}Example;
根据上面可知,a作为第一个成员,对齐到结构体的起始地址,b的对齐数为1,c的对齐数为2
那么他们的存放情况如下:
因为成员c选取的对齐数为2,而5不是2的整数倍,所以成员c不能放置在偏移量为5的位置,向后找到6,它是2的整数倍,所以成员c放置在偏移量为6的位置
成员b和成员c之间的空间就被浪费掉了
当然,这种浪费是有意义的,它可以让计算机读取数据时速度更快
3、结尾:结构体的总大小为最大对齐数的整数倍
最大对齐数就是一个结构体里所有成员中选取的最大对齐数(不包括默认对齐数)
看一下上面的第一个例子:
struct Type
{
int a;
int b;
char c;
}Example;
成员a选取的对齐数为4,成员b选取的对齐数为4,成员c选取的对齐数为1,所以最大对齐数为4
那么这个结构体总共要占用多少内存呢?
我们知道它的内存占用情况是:
9不是最大对齐数4的整数倍,所以向后找到10,10也不是最大对齐数4的整数倍........
一直找到12符合条件,所以这个结构体所占内存的大小是12字节
struct Type
{
int a;
int b;
char c;
}Example;
int main()
{
printf("%d\n", sizeof(Example));
return 0;
}
将上述结构体大小进行打印,结果为:
4、如果结构体中嵌套了结构体,嵌套的结构体对齐到自己的最大对齐数的整数倍处,嵌套的结构体作为成员选取的对齐数和自己的最大对齐数相等
怎么理解呢?我们先来看看嵌套的结构体,它的具体情况如下:
struct Son
{
int a;
int b;
char c;
};
它的最大对齐数为:4
它所占用的内存(字节)为:12
我们再看看嵌套了它的结构体的具体情况:
struct Father
{
int b;
struct Son a;
char c;
};
a作为成员,它所占的字节数为12,大于默认字节数8,按照之前的规则它选取的对齐数应该是8,那么结构体类型Father的最大对齐数应该为8
但是它是一个结构体,它选取的对齐数就是自己的最大对齐数,也就是上面的4,那么结构体类型Father的最大对齐数实际上为4
结构体类型Father的内存占用情况如下:
嵌套结构体a的最大对齐数为4,4正好是它的整数倍,所以对齐到4
而结构体类型Father的最大对齐数为4,所占用的内存大小应该是20个字节
二、总结
成员自身的对齐数和默认对齐数 决定 成员选取的对齐数 决定 最大对齐数