内存对齐
计算机在读取数据时,如果一次读取一个字节,读取一个四字节的数据就需要读取 4 次,实际上,有可能的读取字节数是1,2,4,8,16,以一次读取2个字节为例,也就是说,所有的数据都是两个字节相当于一个读取单位。
0 | 1 | 2 | 3 |
---|---|---|---|
0 | 1 | 2 | 3 |
在我们看来,计算机能从任意位置处开始读取数据,实际上,在以两个字节为例的情况下,并不是这样的,而是只能直接读取以0和2开始的两个字节的数据,就是认为0和1是一个读取的数据,2和3是一个数据,不能从1处开始读取数据。如果要读取从1开始的两个字节的数据,就需要先读取0和1,再读取2和3处的数据,进行处理后,才能够得到1开始的两个字节的数据。
结构体大小
偏移量
偏移量是结构成员相对于结构开头的字节偏移数。offsetof可以计算偏移量,在使用offsetof时,需要包含stddef.h这个头文件。
第一个成员在与结构体变量偏移量为0的地址处。其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值。
#include <stdio.h>
#include <stddef.h>
struct S
{
int n;
double f;
};
int main()
{
printf("%zd\n", offsetof(sruct S, n));//0
printf("%zd\n", offsetof(sruct S, f));//8
printf("%zd\n", sizeof(int));//4
printf("%zd\n", sizeof(double));//8
printf("%zd\n", sizeof(struct S));//16
return 0;
}
该例中int占据4个字节,double占据8个字节,如果默认对齐数是4,struct S中的n的空间是0 ~ 3,f 是4 ~ 11,总共是12个字节,但sizeof(struct S)的结果是16,满足默认对齐数是8的情况,也就是说VS2022的默认对齐数是8。
结构体的大小是所有变量中最大对齐数的整数倍
struct S
{
int n;
char ch;
};
int main()
{
printf("%zd\n", sizeof(struct S));//8
return 0;
}
n的偏移量是从0 ~ 3,ch 的偏移量只有4,因为结构体是所有变量中最大对齐数的整数倍,也就是4的整数倍,所以sizeof(struct S)的结果是8。
结构题S1嵌套了结构体S2,S2的对齐数是S2的变量中的最大对齐数,S1的大小是所有最大对齐数(含S2的对齐数)的整数倍。
struct S2
{
char ch;
int n;
};
struct S1
{
char ch;//0
struct S2 s;//4~11
};
int main()
{
printf("%zd\n", sizeof(struct S2));//8
printf("%zd\n", sizeof(struct S1));//12
return 0;
}
S2的对齐数是int的大小,也就是4,s的偏移量是4 ~ 11的位置,S1的大小是12。
修改默认对齐数
#pragma 这个预处理指令可以修改默认对齐数。
#pragma pack(4)
struct S1
{
int n;
double f;
};
#pragma pack()
struct S2
{
int n;
double f;
};
int main()
{
printf("%zd\n", sizeof(struct S1));//12
printf("%zd\n", sizeof(struct S2));//16
return 0;
}
#pragma pack(4)将VS的默认对齐数改为了4,所以S1的大小就是12,而#pragma pack()又将VS的默认对齐数改为默认值,也就是8,所以S2的大小是16。