所谓内存对齐,是为了让内存存取更有效率而采用的一种编译阶段优化内存存取的手段。
在Windows系统下,C/C++一般对于int、float、double这种变量分配的内存和类型本身的大小是一致的,也就是分别是4、4和8个字节。
但是对于Struct或者是Class类型就不一样了,遵循着以下的原则:
1.为成员变量分配内存的顺序是按照声明的顺序
2.每一个成员变量的偏移值必须是该变量所属类型字节的倍数,第一个成员变量的偏移值是0
3.结构体整体的大小必须是里面变量类型最大值的整数倍
下面我们来看一个例子:
struct A
{
char m1;
unsigned int m2;
char m3;
double m4;
char m5;
};
这个结构体类型的变量如果直接按照子成员大小累加的话,那么是15。但按照上面写的原则来看的话,那么应该是:
第一个变量m1偏移是0,没啥说的,占位为1。
m2的大小为4,为了保证原则2(偏移值必须是该变量所属类型字节的倍数),必须在m1之后为它多分配三个字节的空间,那么m2的偏移为4。
m3接在m2之后,偏移为5。
m4本身大小为8,为了保证原则2,那么在m3之后需要多留7个字节的空间,所以m4偏移为16。
m4偏移为25,但是为了满足原则3,需要在m4之后分配7个字节的空间(结构体中最大的变量是m4)。
所以,sizeof(A)实际的值是32。
如果能够合理安排结构体成员变量的顺序,按照变量大小从小到大的顺序排布的话,A的实际大小可以调整得更小一些,例如下面这样:
struct A{
char m1;
char m5;
char m3;
unsigned int m2;
double m4;
};
sizeof(A)的值就变成了16,比之前节省了一半的空间。
那么有人可能会问,为什么要采用这种看起来“冗余”的内存分配方式呢?这实际上和CPU的读取有关。
对CPU来说,它对内存的访问并不是按照一个字节一个字节来的,而是以一块一块进行读取的,块的大小可以是2,4,8,16字节大小。如果变量进行对齐的话,那么读取一个int只需要直接读取一个内存块即可;如果变量不对齐,那么CPU读取一个int可能需要访问两次内存块,并将两个内存块中的部分内容拼接成一个int,这样就大大浪费了CPU处理的时间。甚至对于有些CPU来说,它对于不对齐的数据会直接抛出异常报错。
所以说,经过内存对齐后,可以大大提升CPU的内存访问速度,这就是对齐存在的必要性。