内存对齐程浩知识点一、不涉及位域的内存对齐原则二、涉及位域的内存对其原则三、成员变量含有结构体的内存对齐情况四、要求内存对齐的原因及优点一个例子#includeiostream2usingnamespacestd;3structA4{5charm;6intn;7};8intmain()9{10Aa;11a.m=1;a.n=2;12printf(sizeof(A)=%d\nsizeof(A.m)=%d\nsizeof(A.n)=%d\n,sizeof(a),sizeof(a.m),sizeof(a.n));13return0;14}预测输出:sizeof(A)=5sizeof(A.m)=1sizeof(A.n)=4实际输出:sizeof(A)=8sizeof(A.m)=1sizeof(A.n)=4一个例子sizeof(A.m)=1,sizeof(A.n)=4,sizeof(A)不是该1+4=5吗,怎么是8呢?这是因为程序员眼中的内存与处理机处理内存的不一致,程序员通常认为内存就是一些列简单的字节数组,在C语言以及它的衍生语言中,char*被普遍认为代表一块内存区域,即使是Java也用byte[]来代表原始内存如下图所示:处理机在对内存进行存取操作时,却不是以单个字节为单位来进行的,它通常是以2、4、8、16大小的字节块来进行的。我们称处理器访问内存时一次存取的内存大小为“内存访问粒度”,如下图所示:一个例子可以看一下内存中的情况,确实是8个字节:一、不涉及位域的内存对齐原则规则如下,然后来举例说明:1)对结构的数据成员,第一个数据成员位于偏移为0的位置,以后每个数据成员的偏移量必须是MIN(#pragmapack()指定的数,此数据成员的自身长度)的倍数,我们称MIN(#pragmapack()指定的数,此数据成员的自身长度)为该结构成员的对齐模数。注:程序中通常是不指定#pragmapack()它的,默认值为8,从哪里可以看到呢,看下图(发现用图说话有时是直观方便易懂……):一、不涉及位域的内存对齐原则2)为结构体的一个成员开辟空间之前,编译器首先检查预开辟空间的首地址相对于结构体首地址的偏移是否是本成员的整数倍,若是,则存放本成员,反之,则在本成员和上一个成员之间填充一定的字节(填充字节为CC,也就是int3中断),以达到整数倍的要求,也就是将预开辟空间的首地址后移几个字节。3)在数据成员完成各自对齐之后,结构(或联合)本身也要进行对齐,对齐将按照MIN(指定的#pragmapack()的数值,MAX(结构(或联合)数据成员长度))进行对齐。一、不涉及位域的内存对齐原则这里,我们采用系统默认的#pragmapack(8),则c的对齐模数为:MIN(sizeof(char),8)=MIN(1,8)=1i的对齐模数为:MIN(sizeof(int),8)=MIN(4,8)=4s的对齐模数为:MIN(sizeof(short),8)=MIN(2,8)=2根据规则一:一、不涉及位域的内存对齐原则根据规则二:当给c开辟空间以后,为i开辟空间之前,编译器先检查与开辟空间的首地址,发现为1,不是i的对齐模数的整数倍,则向后找,一直到地址4时,以4为起始,向后开辟四个字节大小的空间,而之前的1,2,3这三处则填充CC,如图:根据规则三:现在进行结构体对齐,整个sizeof(A)=10,而MIN(8,MAX(sizeof(c),sizeof(i),sizeof(s)))=4,则sizeof(A)=MIN(4N),并且sizeof(A)=10,故,sizeof(A)=12.再将10,11空间给填充上CC:要知道为什么有的是CC,有的是00啊,还有,千万别看到sizeof(A)=12,就把12位置也给填上CC啊一、不涉及位域的内存对齐原则运行结果,果然跟分析一致二、涉及位域的内存对齐1)若相邻成员变量类型相同,且其位宽之和不大于成员变量类型位宽(在此严重强调,是类型位宽而不是成员变量sizeof,也不是类型的sizeof或者其他什么)大小,则后面的字段将紧邻前一个字段存储,直到不能容纳为止;2)如果相邻位域字段的类型相同,但其位宽之和不大于成员变量的类型宽度大小,则后面的字段将从新的存储单元开始,其偏移量为其类型大小的整数倍;3)如果相邻的位域字段的类型不同,不同位域字段存放在不同的位域类型字节中;(这一条其实会根据编译器的不同而采用不同的规则,此处以VC++6.0标准来说明)4)其实不存在4),这一条是说,在此,还要遵守不涉及位域的2),3)条规则,也部分遵守不涉及位域的第一条规则涉及位域的内存对齐规则:二、涉及位域的内存对齐1.从起始地址(假设为零)为c1分配空间,如下图:2.接下来,为c2分配空间,由于c1和c2的类型都为char,且c1和c2的位宽之和为8不大于类型char的位宽8,根据规则1,c2的空间分配如下:二、涉及位域的内存对齐3.接下来,为s1分配空间,此时,有一个问题,s1的内存空间的起始地址是从哪儿开始呢?1还是2?根据上一节介绍的我们可知,s1的对齐模数为2,此处上一节的规则规则依然适用,结合这里的第三条,1处,填充CC,从2处开始分配:4.接下来,为s2分配空间,重复第二步,非配如下(此处也要注意一下的,因为一个s2的空间非配跨越了两个字节,而且都不是完整的占有一个字节):5.接下来为i分配内存空间便如同上一节讲的一样了,如下图:6.我们看最终的内存空间分配结果:最终结果:sizeof(A)=8小练习三、成员变量含有结构体的内存对齐情况1)不涉及位域的内存对齐原则中的三条2)结构体中作为成员变量的结构体的”对齐模数”为该结构体变量中成员变量的sizeof最大值,即:Structtemp{intt;charc;…}Structa{tempt;}则在a中temp的对齐模数为MAX(sizeof(int),sizeof(char),…)三、成员变量含有结构体的内存对齐情况过程如下:1、在Aa;处设断点,Go;2、找到a在内存空间中的地址为0012ff38,如下图:3、查看现在0012ff38起始的内存空间存储情况:4、单步调试如下图(反汇编代码+内存空间分配+注释):四、内存对齐的原因和优点需要内存对齐的原因:如果你不理解和处理软件中的对其问题,以下糟糕的情况在你的软件中将会越来严重:1)软件运行速度越来越慢2)应用程序被锁定3)操作系统崩溃4)软件会因错误的结果而逐渐被淘汰内存对齐的优点:1)实现平台无关:不是所有的硬件平台都能访问任意地址上的任意数据;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。2)内存对齐以后,处理器的读取速度将会大大提高四、内存对齐的原因和优点为了阐明内存对齐背后的规则,我看一个常见的小问题,并且来看一下不同的内存读取粒度对这个问题有什么影响。这个问题很简单:1)从地址0读取四个字节到处理器的寄存器2)从地址1读取四个字节到处理器的寄存器首先我们来看一下当处理器的内存读取粒度为1时的情况:这符合天真的程序员所认为的内存工作的模式:从地址0和从地址1开始的四次内存访问是相同的。下面来看一下处理器的内存读取粒度为2时的情况:四、内存对齐的原因和优点内存读取粒度为2时内存读取次数是粒度为1时次数的一半。而每次内存读取都需要固定数额的开销,所以尽量减少内存读取次数将会有效的帮助处理器提高性能。不过,请注意当从地址1开始读取时的情况。由于这个地址没有均匀的落在处理器的内存读取边界上,处理器就需要做一些额外的开销。这样的地址就被称为未对齐地址(暂且这样翻译,我看到原文是斜体字,应该是专有名词,不知道是不是这样翻译的,如果不是的话,请留言或回复说一下,谢谢了~)。因为地址1是未对齐的,当存取粒度为2时,处理器就要做一次额外的内存读取操作,这样就会减低操作的效率。最后呢,我们来检测一下内存读取粒度为为4时的情况:可以看得出来,内存存取粒度为4的处理器能够快速的一次从对齐的地址中读取四个字节。而从为对齐的地址中却需要翻倍的次数。这里细说一下粒度为2从地址1开始的情况,此时,CPU先访问一次内存,读取0-1自己的数据进寄存器,并再次读取2-3字节的数据进寄存器,接着把字节0和字节3的数据剔除,合并1,2字节的数据进寄存器。