内存地址对齐

  • Post author:
  • Post category:其他

  1. 什么是内存地址对齐?

内存地址对齐是计算机在内存中的数据排列、访问数据的方式,包含了基本数据对齐和结构体数据对齐的两种相互独立又相互关联的部分。现代计算机在内存中读写数据是按字节块进行操作,理论上任意类型的变量访问可以从任何地址开始,但是计算机系统对任意数据类型在内存中存放位置有限,它会要求这些数据的首地址的值为K(4位或者8位)的整数倍。

如在32位操作系统中,数据总线宽度32,每次读取4字节,地址总线宽度为32,故最大寻址空间为2^32=4GB,但是最低位A[0]、A[1]不用寻址,A[2]、A[3]才能与存储器相连,总的寻址位还是2^30=4GB。在内存中存放的基本数据类型的首地址的最低两位都是0(结构体中的成员变量除外)。

基本类型数据对齐是数据在内存中的偏移地址必须为一个字的整数倍,这种存储数据的方式,可以提升系统在读取数据时的性能。

在结构体中为了对齐数据,可能必须在上一个数据结束和下一个数据开始的地方填充一些不必要的字节。

  1. 内存存取粒度

C语言及衍生语言中,char *用来指代一块内存,在Java中也有byte[]类型来指代物理内存,内存是以字节为单位。但是大部分的处理器不是按字节块来存取内存,一般会以双字节、4字节、8字节、16字节以及32字节为单位来存取内存,这些存取单位称之为存取粒度。

若不理解内存对齐,在编写程序时可能产生下面的问题:

  1. 程序运行速度变慢;
  2. 应用程序产生死锁;
  3. 操作系统崩溃;
  4. 程序会毫无征兆的出错,产生错误的结果。
  1. 内存对齐基础

内存存取粒度为1字节的处理器:

    

存取粒度为双字节的处理器:

从地址0读取数据,双字节存取粒度的处理器读取内存的次数是单字节粒度的处理器读取内存次数的一半。在每次读取内存时都会产生一个固定的开销,最小化的内存存取次数将提升程序的性能。

但是从地址1读取数据时,地址1没有与处理器的内存存取边界进行对齐,处理器会增加额外的开销,地址1这样的地址被称作非对其地址。由于地址1是非对齐的,双字节存取粒度的处理器必须再读一次内存,才能获得想要的4个字节数据,这减缓了操作速度。

存取粒度为4字节的处理器:

在对齐内存的基础上,四字节内存粒度处理器可一次性将4个字节全部读出。而非对齐内存上,读取次数将加倍。

  1. 内存对齐规则

每个操作平台上的编译器都有自己默认的对齐系数(也称对齐模数)。gcc中默认#pragma pack(4),可以通过预编译命令# pragma pack(n),n=1,2,4,8,16来改变这一系数。

有效对齐值:是给定值#pragma pack(n)和结构体中最长数据类型长度中较小的那个。有效对齐值也叫对齐单位。

内存对齐遵循的原则:

  1. 第一个成员的首地址为0;
  2. 每个成员的首地址是自身大小的整数倍。例如:以4字节对齐为例,如果自身大小大于4字节,都以4字节整数倍为基准对齐;
  3. 结构总体对齐。例如:以4字节为例,取结构体中最大成员类型倍数,如果超过4字节,都以4字节整数倍为基准对齐。

例如:

假设以32位平台并且以4字节对齐

#pragma pack(4)

struct {

    char x;

    int y;

short f;

    int z;

    char a[5];

    double c;

char h;

}s;

下图为对齐后的结构如下:

对齐步骤方式如下:

  1. 第一个成员x:假设把x放到内存开始地址为0的 位置,x占一个字节,所以x占用地址范围为0-1;
  2. 第二个成员y:由与y为整型,占4个字节。又因为第一个成员x结束地址为1,地址1不是4的整数倍,需要再加4个字节,地址从4开始放成员y;
  3. 第三个成员为f:f为short,占2字节,成员y的结束地址为8,地址8是2的整数倍,所以地址8后直接放成员f;
  4. 第四个成员z:z为整型,占4个字节。而成员f的结束地址为10,地址10不是4的整数倍,需要加2个字节,地址从12开始放成员z;
  5. 第五个成员a[5]:由于char类型占一个字节,而成员z的结束地址为16,任意地址是1的整数倍,a[5]占5个字节,从地址16开始存放;
  6. 第六个成员c:c为double型,占8个字节。因为本文采用以4字节对齐方式,8字节大于4字节,所以以4字节对齐为基准。又成员a[5]的结束地址为21,地址21不是4的整数倍,需要加3个字节,从地址24开始存放成员c;
  7. 第七个成员h:h为char型,占一个字节,任意地址为1的整数倍,即从地址32存放成员h。

经过上面的7步,内存对齐就完成了,但是还未对结构体进行补齐。补齐的原则为:以4字节对齐为例,取结构体中最大成员类型倍数,如果超过4个字节,都以4字节整数倍为基准对齐。

在上述结构体中最大类型为double类型,占8字节,又8字节大于4字节,所以还是以4字节为基准对齐。整个结构体的结束地址为33,而地址33并不是4的整数倍,所以需要添加3个字节来填充结构体,如下图蓝色部分为补齐的空间。

到此为止内存对齐与补齐就完成了。