C++知识积累:内存对齐理解

版权声明:转载请注明出处!谢谢! https://blog.csdn.net/qq_28114615/article/details/88054547

为什么要进行内存对齐?

       比如CPU是32位的,那么就有32根地址线,32根数据线,取数时,CPU的32根据地址线与内存的0-3号地址对齐,CPU32位数据线也同样,一个读取周期只能取这0-3地址的3个字节。如果你是取3-4地址的数据,CPU会自动把它分解成2次取数据操作,一次取8位的3单元和一次取8位4单元数据。只有开始地址是0、4、8...的32位的数据操作才能一次操作完成,内存不支持从1号单元开始的4字节读,CPU和内存的数据线必须相应数据线对齐才行。
        换句话说,如果操作1字节的数据,可以是任意地址,如果是操作2字节的数据,如果开始地址在偶数地址,一次就可以取2字节,如果开始地址在奇数,就要2次内存操作才能完成;如果操作4字节的数据,最好开始地址在能被4整除的数值上,这样可以用一条32位的内存操作指令完成。同样,8字节的开始位置最好的能被8整除的数值上,这样可以用一条64位的内存操作指令完成。
       也就是说,如果对齐了,一次就可以完成,不对齐,就可能多次才能完成。
       另一方面,编译器编译程序处理时也会有对齐处理,一般的结构体和对象等都有对齐的处理(把结构体或对象的开始位置定在边界上),这样,为了让结构体或者对象中的数据能够快速读取,编译器就会对这些数据进行内存对齐。

怎样进行内存对齐?

内存对齐原则:由数据成员本身大小以及#parama pack(n)的n值决定。有以下3个原则:
①第一个数据成员的偏移地址地址为最长数据成员长度的整数倍,即对齐长度为最长数据成员长度;(如果是含有虚函数的类,那么会在开头放置虚表指针,然后才是第一个数据成员)
②每一个数据成员相对于首地址的偏移量为该数据成员长度的整数倍,即对齐长度为该数据成员长度;(由于最长数据成员长度必定是每个数据成员长度的整数倍,因此②是兼容①的)
③所有数据成员对齐后,还要保证整个类/结构体/联合体的长度为最长数据成员长度的整数倍,即对齐长度为最长数据成员大小;

     如果设置了#pragma pack(n),那么这3个原则将取各自原对齐长度和n中的较小值作为对齐长度。

内存对齐举例

class A
{
	int x;
	char y;
	int z;
	double p;
	virtual void f();
};

       在类A中声明了一个虚函数,因此类A的开头会放置一个虚表指针,大小为4个字节,如果不对齐,那么第一个数据成员x的偏移量就为4,但是这样就违背了对齐原则①,因此会在虚表指针后补齐4个字节,让第一个数据成员x的首地址偏移为8,因此x就占据了8~11;

       此时第二个数据成员y的偏移就是12了,y的大小为1,因此是符合对齐原则的,y就占据了12;

       此时第三个数据成员z的偏移就是13了,z的大小为4,不符合对齐原则②,因此需要补齐3个字节,使得z的偏移量为16,z就占据了16~19;

       此时第四个数据成员p的偏移就是20了,p的大小为8,不符合对齐原则②,因此需要补齐4个字节,使得p的偏移量为20,p就占据了20~27;

       此时所有数据相对于首地址来说占据的内存地址为0~27,大小为28个字节,根据对齐原则③,类中最大数据成员大小为8,因此还需要在最后补齐4个字节,因此实际类占据的内存地址为0~31,大小为32个字节。

       关于此处的类A的内存分布可在VS-项目-属性-命令行中输入/d1 reportSingleClassLayoutA (查看全部分布情况用/d1 reportAllClassLayout),然后运行程序后查看,如图所示。

 

猜你喜欢

转载自blog.csdn.net/qq_28114615/article/details/88054547