在开始介绍之前我们先来看一串代码
#include<stdio.h>
struct s1
{
char a1;//1字节
int i;//4字节
char a2;//1字节
};
struct s2
{
char a1;//1
char a2;//1
int i;//4
};
int main()
{
printf("%d\n", sizeof(struct s1));
printf("%d\n", sizeof(struct s2));
return 0;
}
sizeof操作符求内存大小正常来说都是6字节
真正的运行结果↓

一个12 一个8 结果不一样甚至都没有我们预期的6 这里就要说到结构体独特的存储方式(内存对齐)
结构体的内存对齐遵循以下规则↓
1.结构体的第一个成员,对齐到结构体在内存中存放位置的0偏移处
2.从第二个开始,每个成员都要对齐到(一个对齐数)的整数倍处。 对齐数:结构体成员自身的大小和默认对齐数的较小值。VS环境下默认的是8。Linux gcc环境下没有默认对齐数,对齐数就是数据结构体成员的自身大小
3.结构体的总大小,必须是所有成员对齐数中最大对齐数的整数倍
4.如果结构体中嵌套了结构体成员,要将嵌套的结构体成员对齐到自己的成员中最大的对齐数的整数倍处。
注:结构体的总大小必须是最大对齐数的整数倍,最大对齐数就是包含嵌套结构体成员中的对齐数的所有对齐数的最大值
刚看可能有一点蒙圈 根据这些来理解最开始的代码

根据存储规则来解释一下s1 为什么是12字节
注:上图右侧每一个空格代表一个字节大小
结构体第一个成员char a1存放在内存的0偏移处大小1字节也就是红色的格子
第二个成员int i 并不是从1开始存储 而是从4开始也就是灰色的地方(4字节小于默认值8字节取最小值4为对齐数) 这里就要参考第二条规则 对齐到对齐数4的整数倍处也就是4的位置
黑色代表空间被浪费了(有意义的浪费后面会讲到)
再存储第三个成员char a2, a2是1个字节的大小默认值是8 取最小值1为对齐数 下一个存储位置是8而8是对齐数1的整数倍所以从8开始存储a2占一个字节
最后一步参考第三条结构体的总大小是结构体成员最大对齐数的整数倍,s1的最大对齐数也就是int(4字节) 存储完a2也就是从0-8一共九个字节的大小 不是4的整数倍就要继续占用内存直至到11(0-11)12字节的大小是最大对齐数4的倍数
下面三个字节空间(黑色)被浪费掉了
结构体经过内存对齐之后的大小就是12字节
对于struct s2的大小为什么是8大家可以根据结构体的内存对齐规则和struct s1的案例也可以正确的画出内存存储的就构图来
***对于第四条规则的结构体嵌套 我们给出struct s3 里面嵌套了struct s1
#include<stdio.h>
struct s1
{
char a1;//1字节
int i;//4字节
char a2;//1字节
};
struct s2
{
char a1;//1
char a2;//1
int i;//4
};
struct s3
{
char a;//1
struct s1;//12
double b;//8
};
int main()
{
printf("%d\n", sizeof(struct s1));//12
printf("%d\n", sizeof(struct s2));//8
printf("%d\n", sizeof(struct s3));//24
return 0;
}
struct s3的大小是24 接下来给出解释

在了解上图的基础上 根据这个图应该很容易就能理解 对于嵌套的struct s1如何存储 我们已知struct s1结构体成员最大对齐数是4 所以从4的位置开始存储(前面三个字节空间浪费掉)struct s1(由前面求得大小12字节) 直至存够12个字节(4-15)
double 类型大小8字节从16开始存储正好是8的倍数从,存储够8字节(16-23)
最后总大小是最大对齐数的整数倍包括嵌套的结构体里面的 最大的也就是double(8) ,24(0-23)也就是8的整数倍符合规则所以struct s3所占的内存空间大小就是24字节
对于为什么结构体存储要有内存对齐这种规则 这不是浪费内存空间吗?
其实主要有两点原因:
1.平台原因(移植原因):不是所有的硬件平台都能访问任意地址上的任意数据的,某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
2.性能原因:数据结构(尤其是栈)应该尽可能地在自然边界上对齐。对于未对齐的内存 处理器需要操作两次内存访问,而对齐的内存访问仅仅需要一次访问。
总的来说就是结构体的内存对齐就是拿空间来换取时间的做法
最后补充一点在VS中的默认对齐值是可以修改的 需要一个宏#pragma pack()
#include<stdio.h>
#pragma pack(1)
struct s1
{
char a1;//1字节
int i;//4字节
char a2;//1字节
};
struct s2
{
char a1;//1
char a2;//1
int i;//4
};
int main()
{
printf("%d\n", sizeof(struct s1));//12(原)
printf("%d\n", sizeof(struct s2));//8(原)
return 0;
}
来看看默认对齐值改成1 结构体所占内存大小会如何变化↓.

结果都变了 可以试着自己画图理解
想要恢复哪个结构体的默认值就在哪个结构体前面加上#pragma pack()口号内不要加东西
例如恢复struct s2就在其卡面加
#include<stdio.h>
#pragma pack(1)
struct s1
{
char a1;//1字节
int i;//4字节
char a2;//1字节
};
#pragma pack()//括号内不要填东西
struct s2
{
char a1;//1
char a2;//1
int i;//4
};
int main()
{
printf("%d\n", sizeof(struct s1));//12(原)
printf("%d\n", sizeof(struct s2));//8(原)
return 0;
}
看一下结果

可以看到struct s2恢复了