内存对齐

虽然所有的变量最后都会保存到特定地址的内存中,但相应的内存空间必须满足内存对齐的要求。主要出于两个方面的原因:

  • 平台原因:不是所有的硬件平台(特别是嵌入式系统中使用的低端微处理器)都能访问任意地址上的任意数据,某些硬件平台只能访问对齐的地址,否则会出现硬件异常
  • 性能原因:如果数据存放在未对齐的内存空间中,则处理器访问变量时需要做两次内存访问,而对齐的内存访问仅需要一次访问。

在32位微处理器中,处理器访问内存都是按照32位进行的,即一次读取或写入都是4个字节,比如,地址0x0 ~ 0xF这16字节的内存,对于微处理器来说,不是将其看作16个单一字节,而是4个块,每块4个字节,详见图2.4。

显然,只能从0x0、0x4、0x8、0xC等地址为4的整数倍的内存中一次取出4个字节,并不能从任意地址开始一次读取4个字节。(个人:先作为结论记住)

假定将一个占用4字节的int类型数据存放到地址0开始的4字节内存中,其示意图详见图2.5。

由于int类型数据存放在块0中,因此CPU仅需一次内存访问即可完成对该数据的读取或写入。反之,如果将该int类型数据存放在地址1开始的4字节内存空间中,其示意图详见图2.6。

此时,数据存放在块0和块1两个块中,若要完成对该数据的访问,必须经过两次内存访问,先通过访问块0得到该数据的3个字节,再通过访问块1得到该数据的1个字节,最后通过运算,将这几个字节合并为一个完整的int型数据。

由此可见,若数据存储在未对齐的内存空间中,将大大降低CPU的效率。但在某些特定的微处理器中,它根本不愿意干这种事情,这种情况下,就出现系统异常,直接崩溃了

内存对齐的具体规则如下: 

  • 结构体各个成员变量的内存空间的首地址必须是“对齐系数”和“变量实际长度”中较小者的整数倍。假设要求变量的内存空间按照4字节对齐,则内存空间的首地址必须是4的整数倍,满足条件的地址有0x0、0x4、0x8、0xC……
  • 对于结构体,在其各个数据成员都完成对齐后,结构体本身也需要对齐,即结构体占用的总大小应该为“对齐系数”和“最大数据成员长度” 中较小值的整数倍

一般来说,对齐系数与微处理器的字长相同,比如,32位微处理器的对齐系数是4字节,变量的实际长度与其类型相关,计算类型长度的方法如下: 

printf("sizeof(char)   = %d\n", sizeof(char)); 

printf("sizeof(int)          = %d\n", sizeof(int)); 

printf("sizeof(long)        = %d\n", sizeof(long));

printf("sizeof(float)        = %d\n", sizeof(float)); 

printf("sizeof(double)     = %d\n", sizeof(double)); 

该程序的输出为:1、4、4、4、8。

假定CPU为32位微处理器,对齐系数为4,结构体变量data的定义如下:

struct data_test {

       char              a;

       short            b;

       char              c[2];

       double  d;

       char              e; 

       int          f; 

      char              g

}data;

结构体的各个成员都是从结构体首地址(其由编译器保证必然满足内存对齐的要求,假定为0)开始计算,按照定义的顺序依次存放各个成员,详见表2.1。  

实际存放位置使用[x,y]表示,x表示起始地址,y表示结束地址。如果x与y相等,则直接使用[x]表示。

  • 以成员b为例,其长度为2,小于对齐系数,因此按照2字节对齐,就要求其地址必须是2的倍数,地址0已经被成员a占用,则只能使用满足要求的邻近的内存空间[2,3]存放成员b。而空间[1]由于不满足存放成员b的要求,则只能被弃用。特别地,对于数组成员c,存放时不能将其看作一个整体,即长度为2的成员,应该分别看作两个成员c[0]和c[1]。由此可见,实际存放位置为[0,24],1、6、7、17、18、19部分内存空间被弃用。
  • 当所有成员存放完毕后,则结构体本身也需要对齐,即结构体的大小也应该为对齐字节数的整数倍,对齐字节数取长度最长的成员和“对齐系数”的较小值。在这里,其长度最长的成员为double类型的成员d,其长度为8,大于对齐系数,因此结构体本身也要按照4字节对齐,其占用的空间大小必须是4的整数倍。虽然当前存放位置为[0,24],只占用了25个字节。由于必须满足4的整数倍,因此实际上结构体占用的空间是28个字节,即[0,27]。

验证结构体占用空间大小的方法如下:

printf("sizeof(data)  = %d\n", sizeof(data));

虽然所有成员的总长度为19个字节,但结构体实际占用了28个字节,多余的9个字节空间为内存对齐弃用的空间,即1、6、7、17、18、19、25、26、27,分为4个段:[1],[6,7],[17,19],[25,27]。

查看表2.1可知,这些浪费空间的前面,存放的都是char型数据,由于char型数据只占用一个字节,往往使得其紧接着的空间不能被其它长度更长的数据使用。 为了降低内存浪费的概率,应该在char型数据之后,存放长度最小的成员。即在定义结构体时,应按照长度递增的顺序依次定义各个成员。优化示例结构体的定义如下: 

struct data_test{

       char              a; 

       char              c[2];

       char              e;

       char              g

       short            b;

       int          f;

       double  d;

}data;

类似地,依次存放各个成员,详见表2.2。

所有成员实际存放位置为[0,19],中间的地址为5的内存空间被弃用。由于结构体占用的大小为20个字节,已经是4的整数倍,因此无需再做额外的处理。

结构体只浪费了1个字节空间,使用率达到95%。显然,通过优化结构体成员的定义顺序,在同样满足内存对齐的要求下,可以大大地减少内存的浪费

   

posted on 2022-04-06 11:55  朴素贝叶斯  阅读(498)  评论(0编辑  收藏  举报

导航