1.深入内存对齐
为什么需要内存对齐呢?我们定义一个变量后,就会为该变量在某内存地址处分配一个空间。举例如下:
struct A
{
int d;
char c;
long l;
}
A a={0};//定义一个变量a.
设a 的地址为0,则a.d地址为0,a.c为4,a.l为5。如果空间真的是这样分配的话,会造成一个问题,在X86体系机器上,CPU的一次访存是直接取出32位的值。当我们写出类似 long tmp = a.l 的代码,那么,CPU将会从 a.l 这个地址开始,取出四个字节的数据。先假设内存的设计是,允许从任意地址取值,但若按“规整”的方式取值,效率更高一些。若从 内存地址 5 处开始取值,则会产生两次取值: 第一次取567三个字节的数据,第二次取8字节处的数据。因为,0123,4567,...每四个字节为一个单元,跨单元取值需要多次取。
解决这个问题的方法就是内存对齐。设想,如果任何变量均可以在一次取值内完毕,或者,每一次取值的效率都达到了百分之百(即,每次取值没有浪费的功,没有取白费的数据,如,取32个字节的数据,分为四次取,每次取8字节,这就是效率达到了百分之百),将使程序运行速度得到很大程度的加快。那么,这个过程往往需要人工的干预,即,人为的控制变量在内存中的地址。有2字节对齐,4字节对齐,8字节对齐,等等。你当然可以按任何字节来对齐,但如果要达到访存高效,就要按照内存的访问规律来对齐。之所以会出现,让人手动地指定内存对齐大小,是因为考虑到程序的移植性。多数情况下,编写的程序可能不仅仅在一种平台上运行,还可能在其它不同的平台上运行,内存分布可能不同,各个变量相对地址都可能不同(当然,整个内存模型都可能不同,模型也是从基础的字节构建起来的,我们在此只考虑内存分布细节)。但如果我们限制各个变量的摆放规则,可以使得在不同平台上内存中的变量分布都按一套规则规整化,增强了程序的移植性。这么做还有一个附带的好处,就是规整化,规律化的内存分布,加快内存访问。
现在保证了每次取一个变量都能尽可能地一次取出,由于内存访存时每次是从边界开始访存,有固定的模式,不可随机跳到“任意地址”。所以必须尽可能地将变量的开始地址置于边界,访存边界即对齐值的整数倍地址处(注意是尽可能,并不是每一个变量都对齐到了访存边界)。下面是访存的内存模型, n 是实际内存对齐数。
处理器眼里的内存
处理器访存时,只能从 xn, (x+1)n, (x+2)n ... 等 n 的整数倍地址处开始访存,且处理器一次能读出 n 个字节。
详细分析内存对齐前,先给出几个概念,
1)数据类型自身的对齐值:即类型的大小。
2)指定对齐值:人工指定的内存对齐值,如C语言可以使用#pragma pack (value)时的指定对齐值value。