(一)内存对齐
(1)什么是内存对齐?
通过牺牲空间,来提升访问速度。(拿空间换时间)
举例:环境为32位的系统,假设在内存中只能从4的整数倍处的地址读取数据,地址空间如下图,依次插入int a = 10(大小是4字节),char c = ‘s’ (大小是1字节),int b = 1(大小是4字节)。按照假设的读取规则,三个变量应该按下图所插入。在这个过程中,因为需要遵守读取数据的规则,所以需要牺牲掉一些空间,这就是内存对齐。
(2)为什么要有内存对齐?
本质是因为硬件平台的限制,导致数据读取可能会出现低效率问题,通过“浪费”某些空间的方式,来达到快速读取目标数据的目的。
举例:环境依然是32位,假设在内存中只能从4的整数倍处的地址读取数据,依次插入char a =’s’,int b = 10。但我们这次连续插入,不考虑内存对齐。
在我们读取数据时,因为在内存中只能从4的整数倍处的地址读取数据,地址到1时,char类型读取完毕,接下来要读取出地址在1到4之间的那部分int类型的数据,然后保存,然后继续从地址4开始读取剩下那部分的int类型数据保存,之后再将两部分int类型的数据合并才能得到完整的int型数据。如下图所示:
(3)如何内存对齐?
首先给出内存对齐的规则(也是计算结构体大小的根据):
1.第一个成员在与结构体变量偏移量为0的地址处。
2.其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处
对齐数=编译器默认的对齐数 与 该成员大小的较小值
VS默认对齐数为8
3.结构体总大小为最大对齐数(每个成员变量都有一个对齐数,所有成员变量中最大的对齐数就是结构体的最大对齐数)的整数倍。
4.如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构的对齐数)的整数倍
(二)结构体大小计算
例(1)
求如下代码结构体A的总大小:(以下三个例子的答案均是在win10系统下的VS2019中验证的)
#include <stdio.h>
struct A{
char a;
int b;
};
int main(){
printf("A的大小:%d\n",sizeof(struct A));//结果是 8
}
a的空间:第一个成员变量需要在偏移量为0的地址处,则a的空间是0到1;
b的空间:计算第二个成员变量地址时,根据规则2,首先要计算对齐数,VS默认对齐数是8,该成员大小是4,4小于8,则对齐数是4,地址增加到4时,刚好可以整除对齐数,所以第二个成员所占空间是地址4到8之间。
计算结构体总大小,先求出最大对齐数,所有成员变量中最大对齐数是4,当前结构体所占的空间为8(地址末尾是8,起始是0),8是最大对齐数的整数倍,则总大小是8。图解如下:
例(2)
求如下代码结构体B的总大小:
#include <stdio.h>
struct B {
char a;
int b;
double c;
};
int main() {
printf("B的大小:%d\n", sizeof(struct B));//结果是 16
}
有了上述步骤,接下来不再赘述,直接进行计算。
a的空间:地址0到1处。计算b时,对齐数是4,所占空间是地址4到8处。
c的空间:对齐数是8,所占空间是地址8到16处。
计算结构体总大小:整个结构体最大对齐数是8,结构体此时大小是16,恰好是8的倍数。结构体总大小是16。图解如下:
例(3)
求如下代码结构体C的总大小:
#include <stdio.h>
struct B {
char a;
int b;
double c;
};
struct C {
char m;
double n;
struct B b;
};
int main() {
printf("C的大小:%d\n", sizeof(struct C));//结果是 32
}
这个例子是运用规则四,计算嵌套结构体。
m的空间:地址0到1处。b的空间:对齐数是8,所占空间是地址8到16处。结构体B所占空间:对齐数16,多占空间是16到32处。
计算结构体总大小::整个结构体C最大对齐数是8(有没有好奇为什莫不是16),结构体此时大小是32,32是8的倍数,所以总大小是32。图解如下:
注:在例3中,结构体C的最大对齐数是8,而不是16。因为内部嵌套的结构体B的对齐数是它内部所有成员的对齐数中最大的一个,也就是8。
以上几个例子都恰好直接计算出了总大小,但大多是并不是如此巧合。例如当结构体占了30个空间,最大对齐数是8时,30不是8的倍数,所以需要增加空间到32,所以该结构体总大小是32。
(三)修改默认对其数
#pragma pack (n)//n是要设置的默认对齐数