浅谈内存对齐和位域

来源:互联网 发布:linux route add 网关 编辑:程序博客网 时间:2024/06/05 14:37

参考链接:点击打开链接

地址:http://blog.csdn.net/tht2009/article/details/52117988

内存对齐:

  1. 内存对齐的定义:在现代计算机中一般存储数据,虽然数据类型不同但都可以转换为二进制编码来进行存储。我们对不同的数据进行存储时会耗费一定的内存空间,计算机中内存空间都是按照字节划分的,从理论上讲似乎对任何类型的变量的访问可以从任何地址开始,但是实际上计算机系统对于基本数据类型在内存中的存放位置都有限制,要求这些数据存储首地址是某个数K的倍数,这样各种基本数据类型在内存冲就是按照一定的规则排列的,而不是一个紧挨着一个排放,这就是内存对齐。
  2. 对齐模数:内存对齐中给定的对其参数K值被称为对齐模数(Alignment Modulus),在定义上我们称当一种类型S的对齐模数与另一种类型T的对齐模数的比值是大于1的整数,我们就称类型S的对齐要求比T强(严格),称T比S弱(宽松),反之则称类型S的对其要求比T弱(宽松),称T比S强(严格)。
  3. 内存对齐的一般策略:
    1. Win32平台下的微软C编译器(cl.exe for 80×86)的对齐策略:
      1) 结构体变量的首地址能够被其最宽基本类型成员的大小所整除;
      备注:编译器在给结构体开辟空间时,首先找到结构体中最宽的基本数据类型,然后寻找内存地址能被该基本数据类型所整除的位置,作为结构体的首地址。将这个最宽的基本数据类型的大小作为上面介绍的对齐模数。
      2) 结构体每个成员相对于结构体首地址的偏移量(offset)都是成员大小的整数倍,如有需要编译器会在成员之间加上填充字节(internal adding);
      备注:为结构体的一个成员开辟空间之前,编译器首先检查预开辟空间的首地址相对于结构体首地址的偏移是否是本成员的整数倍,若是,则存放本成员,反之,则在本成员和上一个成员之间填充一定的字节,以达到整数倍的要求,也就是将预开辟空间的首地址后移几个字节。
      3) 结构体的总大小为结构体最宽基本类型成员大小的整数倍,如有需要,编译器会在最末一个成员之后加上填充字节(trailing padding)。
      备注:结构体总大小是包括填充字节,最后一个成员满足上面两条以外,还必须满足第三条,否则就必须在最后填充几个字节以达到本条要求。

      根据以上准则,在windows下,使用VC编译器,sizeof(T)的大小为8个字节。                                                                                                    
    2. 在GNU GCC编译器中,遵循的准则有些区别,对齐模数不是像上面所述的那样,根据最宽的基本数据类型来定。

      在GCC中,对齐模数的准则是:对齐模数最大只能是4,也就是说,即使结构体中有double类型,对齐模数还是4,所以对齐模数只能是1,2,4。而且在上述的三条中,第2条里,offset必须是成员大小的整数倍,如果这个成员大小小于等于4则按照上述准则进行,但是如果大于4了,则结构体每个成员相对于结构体首地址的偏移量(offset)只能按照是4的整数倍来进行判断是否添加填充。
      看如下例子:

      struct T
      {
      char ch;
      double d ;
      };

      那么在GCC下,sizeof(T)应该等于12个字节。

      如果结构体中含有位域(bit-field),那么VC中准则又要有所更改:
      1) 如果相邻位域字段的类型相同,且其位宽之和小于类型的sizeof大小,则后面的字段将紧邻前一个字段存储,直到不能容纳为止;
      2) 如果相邻位域字段的类型相同,但其位宽之和大于类型的sizeof大小,则后面的字段将从新的存储单元开始,其偏移量为其类型大小的整数倍;
      3) 如果相邻的位域字段的类型不同,则各编译器的具体实现有差异,VC6采取不压缩方式(不同位域字段存放在不同的位域类型字节中),Dev-C++和GCC都采取压缩方式;

      备注:当两字段类型不一样的时候,对于不压缩方式,例如:

      struct N
      {
      char c:2;
      int i:4;
      };

      依然要满足不含位域结构体内存对齐准则第2条,i成员相对于结构体首地址的偏移应该是4的整数倍,所以c成员后要填充3个字节,然后再开辟4个字节的空间作为int型,其中4位用来存放i,所以上面结构体在VC中所占空间为8个字节;而对于采用压缩方式的编译器来说,遵循不含位域结构体内存对齐准则第2条,不同的是,如果填充的3个字节能容纳后面成员的位,则压缩到填充字节中,不能容纳,则要单独开辟空间,所以上面结构体N在GCC或者Dev-C++中所占空间应该是4个字节。

      4) 如果位域字段之间穿插着非位域字段,则不进行压缩;
      备注:
      结构体
      5) 整个结构体的总大小为最宽基本类型成员大小的整数倍。

      typedef struct
      {
         char c:2;
         double i;
         int c2:4;
      }N3;

      在GCC下占据的空间为16字节,在VC下占据的空间应该是24个字节。

      ps:

    3. 对齐模数的选择只能是根据基本数据类型,所以对于结构体中嵌套结构体,只能考虑其拆分的基本数据类型。而对于对齐准则中的第2条,确是要将整个结构体看成是一个成员,成员大小按照该结构体根据对齐准则判断所得的大小。
    4. 类对象在内存中存放的方式和结构体类似,这里就不再说明。需要指出的是,类对象的大小只是包括类中非静态成员变量所占的空间,如果有虚函数,那么再另外增加一个指针所占的空间即可。

    5. 1.          内存对齐与编译器设置有关,首先要搞清编译器这个默认值是多少

      2.          如果不想编译器默认的话,可以通过#pragma pack(n)来指定按照n对齐

      3.          每个结构体变量对齐,如果对齐参数n(编译器默认或者通过pragma指定)大于该变量所占字节数(m),那么就按照m对齐,内存偏移后的地址是m的倍数,否则是按照n对齐,内存偏移后的地址是n的倍数。也就是最小化长度规则

      4.          结构体总大小: 对齐后的长度必须是成员中最大的对齐参数的整数倍。最大对齐参数是从第三步得到的。

      5.          补充:如果结构体A中还要结构体B,那么B的对齐方式是选它里面最长的成员的对齐方式         参考文章http://blog.csdn.net/xing_hao/article/details/6678048

        • 总结规则如下: 
          0: 结构体变量的首地址能够被其最宽基本类型成员的大小所整除 
          1: VC6和VC71默认的内存对齐方式是 #pragam pack(8) 
          2: 结构体中每个成员按其类型的对齐参数(通常是这个类型的大小)和指定对齐参数中较小的一个对齐. 
          3:   结构体每个成员相对于结构体首地址的偏移量都是成员大小的整数倍. 
          4:   结构体本身也存在着对齐要求规则,不能比它所有字段中要求最严格的那个宽松. 
          5: 结构体的总大小为结构体最宽基本类型成员大小的整数倍,且应尽量节省内存。 
          6: 在GCC中,对齐模数的准则是:对齐模数最大只能是 4,也就是说,即使结构体中有double类型,对齐模数还是4,所以对齐模数只能是1,2,4。 
                而且在上述的规则中,第3条里,offset必须是成员大小的整数倍: 
                 (1): 如果这个成员大小小于等于4则按照上述准则是可行的, 
                 (2): 如果成员的大小大于4,则结构体每个成员相对于结构体首地址的偏移量只能按照是4的整数倍来进行判断是否添加填充。

      位域:

      • 位域是指信息在存储时,并不需要占用一个完整的字节, 而只需占几个或一个二进制位。例如在存放一个开关量时,只有0和1 两种状态, 用一位二进位即可。为了节省存储空间,并使处理简便,C语言又提供了一种数据结构,称为“位域”或“位段”。所谓“位域”是把一个字节中的二进位划分为几 个不同的区域, 并说明每个区域的位数。每个域有一个域名,允许在程序中按域名进行操作。 这样就可以把几个不同的对象用一个字节的二进制位域来表示
      • 位域的定义形式:
          struct 位域结构名

          { 位域列表 };

      其中位域列表的形式为: 类型说明符 位域名:位域长度

      例如:

      struct bs


      {int a:8;int b:2;int c:6;};

      位域变量的说明

      与结构变量说明的方式相同。 可采用先定义后说明,同时定义说明或者直接说明这三种方式。

      例如:

      struct bs

      {int a:8;int b:2;int c:6;}data;

      说明data为bs变量,共占2个字节。其中位域a占8位,位域b占2位,位域c占6位。

      位域定义的几点说明

      对于位域的定义尚有以下几点说明:

      1. 宽度为 0 的一个未命名位域强制下一位域对齐到其下一type边界,其中type是该成员的类型。例如:

      struct bs {

      unsigned a:4;

      unsigned :0 ;/*空域*/

      char b:4 ;/*从下一单元开始存放*/

      unsigned c:4;

      }data;

      VC6(默认的配置,未作任何优化选择) 对空域的处理。

      实验中,0x0012ff74为变量data的起始地址,位域a填充0x0012ff74的后四位,位域b从0x0012ff78开始,占据0x0012ff78的后四位。所以空域占据了从a开始的4个位剩余部分。

      乍看 VC6对空域的处理是依据空域的类型,即unsigned。其实不然。

      经试验,空域所占大小和 a的类型及 空域的类型 二者皆相关。

      即以下四种情况,

      a,空域皆为char时,二者共占据1字节;

      a 为unsigned,空域为unsigned; a 为char,空域为unsigned; a 为unsigned,空域为char;这三种情况,二者共占据4字节。

      2. 位域的长度不能大于指定类型固有长度,比如说int的位域长度不能超过32,bool的位域长度不能超过8。

      3. 位域可以无位域名,这时它只用来作填充或调整位置。无名的位域是不能使用的。例如:

      struct k

      {int a:1int :2 /*该2位不能使用*/int b:3int c:2};

      从以上分析可以看出,位域在本质上就是一种结构类型, 不过其成员是按二进位分配的。

      原创粉丝点击