结构体的对齐规则
-
第一个成员在结构体变量偏移量为0的地址处。
-
其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处
1) 对齐数 =min( 编译器默认的一个对齐数, 该成员大小)。
2)默认的对齐数,可以通过宏 #pragma pack(N) 指定的值,这里面的 N一 定是2的幂次方.如1,2,4,8,16等。
3)vs中的默认值为8。
4)gcc编译器是没有默认对齐数的,所以 对齐数 = 该成员大小。 -
结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍。
-
如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数
倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)
的整数倍。
第一个例子:
struct S1
{
char c1;
int i;
char c2;
};
int main()
{
struct S1 s;
printf("%d\n", sizeof(s));
return 0;
}
输出
12
结构体内存存储方式如下图:
- 由结构体对齐规则的第一条,可知第一个成员变量c1存储在偏移量为0的地址处, 占用一个字节。
- 由第二条规则,可知第二个成员变量i, 对齐数= min(8, 4), 其中8是VS的默认对齐数,4是int i 的大小,所以对齐数为4。而变量i要存放在对齐数4的整数倍处,则其应该存放在地址4处,且占用4个字节。
- 第三个成员变量**c2, 对齐数= min(8, 1), 对齐数为1, 则c2存放在地址8处
- 由第三条结构体对齐规则,成员变量c1、i、c2的最大对齐数为4,结构体整体大小应该为4的整数倍,而当前c2存放在地址8处,也即是9个字节,所以结构体整体大小应该是4*3=12个字节。
第二个例子
struct S2
{
char c1;
char c2;
int i;
};
int main()
{
struct S2 s2;
printf("%d\n", sizeof(s2));
}
输出
8
结构体内存存储方式如下图:
- 由结构体第一条规则,可知第一个成员变量**c1存放在偏移地址0处,占用一个字节。
- 由第二条规则,可知成员变量c2,对齐数=min(8, 1), 其对齐数的值为 1,则c2存放的偏移地址为1的整数倍,存放在偏移地址1处。
- 由第二条规则,可知成员变量 i,对齐数=min(8, 4), 其对齐数的值为4 ,则i存放在偏移地址为4的整数倍处,即存放在偏移地址4处,占用4个节。
- 由第三条规则,结构体整体大小为最大对齐数4的整数倍,而当前占用了8个字节,正好为4的2倍,符合规则。
第三个例子,嵌套结构体
struct S2
{
char c1;
char c2;
int i;
};
struct S3
{
char c1;
struct S2 s2;
int i;
};
int main()
{
struct S3 s3;
printf("%d\n", sizeof(s3));
}
输出
16
结构体内存存储方式如下图:
- 由结构体第一条规则,可知第一个成员变量c1,存放在偏移地址0处,占用一个字节。
- 由结构体第四条规则:嵌套的结构体对齐到自己的最大对齐数的整数 倍处,而结构体s2的最大对齐数为4,所以其存放在偏移地址4处,占用8 个字节。
- 由结构体第二条规则,可知第三个成员变量 i,对齐数=min(8, 4), 对齐数为4, 所以 i 存放在4*3=12偏移地址处,占用4个字节。
- 由第三条规则,可知结构体的最大对齐数 = max(1, 4, 4), 对齐数为4,而当前已经占用了16个字节,正好为4的整数倍,所以结构体整体大小为 16字节。
pragma pack(N) 修改默认的对齐数
struct S4
{
char c1;
double a;
int b;
};
#pragma pack(4) //设置默认对齐数为4
struct S5
{
char c1;
double a;
int b;
};
#pragma pack() //恢复默认对齐数
int main()
{
struct S4 s4;
struct S5 s5;
printf("%d\n", sizeof(s4));
printf("%d\n", sizeof(s5));
return 0;
}
输出
24
16
由输出结果可知,修改默认对齐数后,其存储方式方式了变化。
结构体struct S4的存储方式如下图:
结构体struct S5(即修改默认对齐数的struct S4)的存储方式如下图:
注意:关于gcc编译器中没有默认对齐数,那么其对齐数=每个成员变量的大小 ,这个和上述类似,就不在这里讲解,大家可以自己尝试这验证。