为什么会出现内存对齐?

    因为当CPU访问内存对齐的数据时,它的运行效率是非常高的。当CPU试图读取的数值没有正确的对齐时,CPU可以执行两种操作之一:产生一个异常条件;执行多次对齐的内存访问,以便读取完整的未对齐数据,若多次执行内存访问,应用程序的运行速度就会慢。所以计算机采用内存对齐的方式来存储数据。

这是高效编程一种很重要的思想:以空间换时间

关于结构体内存对齐(没有指定#pragma pack宏的情况):

规则1:数据成员对齐规则:结构体的数据成员,第一个数据成员放在offset(偏移量)为0的地方,以后每个数据成员存储的起始位置要从该成员大小的整数倍开始(比如int在32位机为4字节,则要从4的整数倍地址开始存储)。

规则2:结构体作为成员:如果一个结构里有某些结构体成员,则结构体成员要从其内部最大元素大小的整数倍地址开始存储。(struct a里存有struct b,b里有char,int,double等元素,那b应该从8的整数倍开始存储。)

规则3:收尾工作:结构体的总大小,也就是sizeof的结果,必须是其内部最大成员的整数倍,不足的要补齐。

看下面这个例子:

void Test1()
{
	struct A
	{
		int a;		//0-3
		double d;	//4-11
		char c;	//12
	};
	cout<<"sizeof(A)= "<<sizeof(A)<<endl;
}

先看规则1:第一个成员放在offset为0的位置,a(0-3),第二个数据d起始位置为第一个成员大小的整数倍开始,所以从4开始,double占8个字节,所以d存放位置offset为:(4-11),接下来的成员c,第一个成员整数倍为它的起始位置,所以起始位置offset为:(12),所以c存放位置(12),这就将数据按内存对齐方式存好了,但是结构体A的大小是多少呢?还要使用规则3:是结构体内部所占字节最大的成员的最小整数倍。,最大成员是d(占8个字节),所以sizeof(A) =  24,结果显示:

wKiom1ca9E3i6CQIAAAK0zYHweA851.png

再看下面这个例子:

void Test2()
{
	struct B
	{
		int a;		//0-3
		char c;	//4
		double d;	//8-15
	};
	cout<<"sizeof(B) = "<<sizeof(B)<<endl;
}

还是先看规则1:第一个成员放在offset为0的位置,a(0-3),第二个数据d起始位置为第一个成员大小的整数倍开始,所以从4开始,char占一个字节,所以成员c存放位置offset为:(4),第三个数据成员起始位置依旧为第一个成员大小的整数倍,起始位置offset为(8),double所占字节为(8),所以d存放位置offset(8-15),再看规则3:结构体总大小,是结构体内部所占字节最大的成员的最小整数倍。最大成员为d,d占8个字节,所以sizeof(B) = 16,显示结果:

wKioL1ca-D_QTJVFAAALhOSm82k810.png

看这个例子:

void Test3()
{
	struct C
	{};
	cout<<"sizeof(C) = "<<sizeof(C)<<endl;
}//[cpp]

class D
{};

void Test4()
{
        cout<<"sizeof(D) = "<<sizeof(D)<<endl;
}

这个大小又是多少呢?哈哈..

因为在C++标准中规定:任何不同的对象不能拥有相同的内存地址。如果空类大小为0,若我们声明一个这个类的对象数组,那么数组中的每个对象都拥有了相同的地址,这显然是违背标准的。

这时编译器就会加一个字节来区别,所以sizeof(C) = 1,sizeof(D) = 1,显示结果:

wKiom1ca_eSBDeDKAAALcWen7E0306.png

总结一下:当结构体中没有嵌套时,只需要使用规则1和规则3就可以计算出结构体大小。

再来看下面这个例子:

void Test5()
{
	struct A
	{
		int a;		//0-3
		char c;		//4
		double d;       //8-15
	};//0-15
	struct B
	{
		A a;		//0-15
		int b;		//16-19
		double c;	//20-27
	};//0-31
	cout<<"sizeof(B) = "<<sizeof(B)<<endl;
}

还是先来看规则1来计算出结构体A的大小为16,在结构体B中,a的大小为16,结构体嵌套时就要运用规则2:结构体a的起始位置offset为结构体A中最大成员的整数倍,所以在结构体B中成员a的起始位置offset为:(0),所占大小为16,所以存放位置offset为(0-15),而B的数据成员依旧按照规则1进行存储:所以b的起始位置offset为(16),存放位置offset为(16-19),B中成员c的起始位置offset为(20),存放位置offset为(20-27),再依据规则3,结构B的总大小为,结构体B中结构体内部所占字节最大的成员的最小整数倍,不足的要补齐offset(28-31),所以sizeof(B) =  32,结构显示:

wKioL1cbCcaA8DkxAAALj2BbmKI626.png

总结一下:当出现结构体嵌套时,首先依据规则1,计算出嵌套的结构体大小,再根据规则2,确定嵌套的结构体存储的起始位置offset,再依据规则1,确定其其他成员的存储位置,最后依据规则3,确定结构体其大小。

当指定了#pragma back宏时

看下面这个例子:

#pragma pack(4)   //指定默认最大对齐数为4
void Test6()
{
	struct A
	{
		char c1;    //0
		int i;      //4-7
		double d;   //8-15
		char c2;    //16
	};
	cout<<"sizeof(A) = "<<sizeof(A)<<endl;
}

当没有指定#pragma back宏时,依据规则1和规则3,sizeof(A) =  24,有了默认最大对齐数,规则3就不在适用了,应该改为指定最大默认对齐数的最小整数倍,所以sizeof(A) = 20。显示结果:

wKiom1cbDhbD_lOmAAALes8dTTM154.png

那么再来看下面这个例子:

#pragma pack(4)
void Test7()
{
	struct A
	{
		char arr[7];
		short s;
	};
	cout<<"sizeof(A) = "<<sizeof(A)<<endl;;
}

由于有了上面的Test6(),就可以知道:sizeof(A) = 12,显示结果:

wKiom1cbD3_C_IxVAAAMYeyRjo8847.png

为什么是10,瞬间懵逼...j_0012.gif

因为结构体A中最大对齐数比默认对齐数小,所以使用规则3时,就使用结构体中的最大对齐数。

总结一下:当有指定默认对齐数时,就要分情况考虑了。

    如果默认对齐数比结构体内部的最大对齐数小,那么使用规则3时,结构体大小就是默认对齐数的最小整数倍;

    如果默认对齐数比结构体内部的最大对齐数大,就直接使用规则3.