内存对齐

来源:互联网 发布:软件开发模块文档 编辑:程序博客网 时间:2024/05/25 05:38

    一、内存对齐的原因

       大部分的参考资料都是如是说的:
      1、平台原因(移植原因):不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
      2、性能原因:数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。

 

    二、对齐规则

      每个特定平台上的编译器都有自己的默认“对齐系数”(也叫对齐模数)。程序员可以通过预编译命令#pragma pack(n),n=1,2,4,8,16来改变这一系数,其中的n就是你要指定的“对齐系数”。

 

      规则:
      1、数据成员对齐规则:结构(struct)(或联合(union))的数据成员,第一个数据成员放在offset为0的地方,以后每个数据成员的对齐按照#pragma pack指定的数值和这个数据成员自身长度中,比较小的那个进行。
      2、结构(或联合)的整体对齐规则:在数据成员完成各自对齐之后,结构(或联合)本身也要进行对齐,对齐将按照#pragma pack指定的数值和结构(或联合)最大数据成员长度中,比较小的那个进行。
      3、结合1、2颗推断:当#pragma pack的n值等于或超过所有数据成员长度的时候,这个n值的大小将不产生任何效果。

 

    三、试验

      我们通过一系列例子的详细说明来证明这个规则吧!
      我试验用的编译器包括GCC 3.4.2和VC6.0的C编译器,平台为Windows XP + Sp2。

      我们将用典型的struct对齐来说明。首先我们定义一个struct:
     
      首先我们首先确认在试验平台上的各个类型的size,经验证两个编译器的输出均为:
      sizeof(char) = 1
      sizeof(short) = 2
      sizeof(int) = 4

 

      我们的试验过程如下:通过#pragma pack(n)改变“对齐系数”,然后察看sizeof(struct test_t)的值。

 

      1、1字节对齐(#pragma pack(1))
      输出结果:sizeof(struct test_t) = 8 [两个编译器输出一致]
      分析过程:
      1) 成员数据对齐
      
      成员总大小=8

      2) 整体对齐
      整体对齐系数 = min((max(int,short,char), 1) = 1
      整体大小(size)=$(成员总大小) 按 $(整体对齐系数) 圆整 = 8 /* 8%1=0 */ [注1]

 

      2、2字节对齐(#pragma pack(2))
      输出结果:sizeof(struct test_t) = 10 [两个编译器输出一致]
      分析过程:
      1) 成员数据对齐
      
      成员总大小=9

      2) 整体对齐
      整体对齐系数 = min((max(int,short,char), 2) = 2
      整体大小(size)=$(成员总大小) 按 $(整体对齐系数) 圆整 = 10 /* 10%2=0 */

 

      3、4字节对齐(#pragma pack(4))
      输出结果:sizeof(struct test_t) = 12 [两个编译器输出一致]
      分析过程:
      1) 成员数据对齐
     
      成员总大小=9

      2) 整体对齐
      整体对齐系数 = min((max(int,short,char), 4) = 4
      整体大小(size)=$(成员总大小) 按 $(整体对齐系数) 圆整 = 12 /* 12%4=0 */

 

      4、8字节对齐(#pragma pack(8))
      输出结果:sizeof(struct test_t) = 12 [两个编译器输出一致]
      分析过程:
      1) 成员数据对齐
     
      成员总大小=9

      2) 整体对齐
      整体对齐系数 = min((max(int,short,char), 8) = 4
      整体大小(size)=$(成员总大小) 按 $(整体对齐系数) 圆整 = 12 /* 12%4=0 */


      5、16字节对齐(#pragma pack(16))
      输出结果:sizeof(struct test_t) = 12 [两个编译器输出一致]
      分析过程:
      1) 成员数据对齐
     

      成员总大小=9

      2) 整体对齐
      整体对齐系数 = min((max(int,short,char), 16) = 4
      整体大小(size)=$(成员总大小) 按 $(整体对齐系数) 圆整 = 12 /* 12%4=0 */

 

    四、结论
     

      8字节和16字节对齐试验证明了“规则”的第3点:“当#pragma pack的n值等于或超过所有数据成员长度的时候,这个n值的大小将不产生任何效果”。另外内存对齐是个很复杂的东西,上面所说的在有些时候也可能不正确。呵呵^_^

 

[注1]:什么是“圆整”?
      举例说明:如上面的8字节对齐中的“整体对齐”,整体大小=9 按 4 圆整 = 12
      圆整的过程:从9开始每次加一,看是否能被4整除,这里9,10,11均不能被4整除,到12时可以,则圆整结束。

 

      原文链接:http://bigwhite.blogbus.com/logs/1347304.html

 

 

 


 

    内存对齐背后的故事

 

      关于内存对齐的中文文章多在介绍对齐的'法则',比如为什么sizeof(T)和我们估计的T的大小有出入呢等等,而对于内存对齐的本质少有介绍,我在Google上搜索了一阵后,在IBM开发社区上发现一篇叫'Data alignment: Straighten up and fly right'的文章,其中就有我想知道的关于'内存对齐背后的故事',下面的很多内容都是来自那篇文章的。

 

      很多书籍中都讲到:内存可以看成一个byte数组,我们通过编程语言提供的工具对这个'大数组'中的每个元素进行读写,比如在C中我们可以用指针一次读写一个或者更多个字节,这是我们一般程序员眼中的内存样子。但是从机器角度更具体的说从CPU角度看呢,CPU发出的指令是一个字节一个字节读写内存吗?答案是'否'。CPU是按照'块(chunk)'来读写内存的,块的大小可以是2bytes, 4bytes, 8bytes, 16bytes甚至是32bytes. 这个CPU访问内存采用的块的大小,我们可以称为'内存访问粒度'。

 

      程序员眼中的内存样子:

      ---------------------------------
      | | | | | | | | | | | | | | | | |
      ---------------------------------
       0 1 2 3 4 5 6 7 8 9 A B C D E F  (地址)

 

      CPU眼中的内存样子:(以粒度=4为例)
      ---------------------------------------------
      | | | | |   | | | | |   | | | | |   | | | | |
      ---------------------------------------------
       0 1 2 3     4 5 6 7     8 9 A B     C D E F  (地址)

 

      有了上面的概念,我们来看看粒度对CPU访问内存的影响。

 

      假设这里我们需要的数据分别存储于地址0和地址1起始的连续4个字节的存储器中,我们目的是分别读取这些数据到一个4字节的寄存器中,如果'内存访问粒度'为1,CPU从地址0开始读取,需要4次访问才能将4个字节读到寄存器中;同样如果'内存访问粒度'为1,CPU从地址1开始读取,也需要4次访问才能将4个字节读到寄存器中;而且对于这种理想中的''内存访问粒度'为1的CPU,所有地址都是'aligned address'。

     

      如果'内存访问粒度'为2,CPU从地址0开始读取,需要2次访问才能将4个字节读到寄存器中;每次访存都能从'aligned address'起始。如果'内存访问粒度'为2,CPU从地址1开始读取,相当于内存中数据分布在1,2,3,4三个地址上,由于1不是'aligned address',所以这时CPU要做些其他工作,由于这四个字节分步在三个chunk上,所以CPU需要进行三次访存操作,第一次读取chunk1(即地址0,1上两个字节,而且仅仅地址1上的数据有用),第二次读取chunk2(即地址2,3上两个字节,这两个地址上的数据都有用),最后一次读取chunk3(即地址5,6上两个字节,而且仅仅地址5上的数据有用),最后CPU会将读取的有用的数据做merge操作,然后放到寄存器中。

 

      同理可以推断如果'内存访问粒度'为4,那么从地址1开始读取,需要2次访问,访问后得到的结果merge后放到寄存器中。

 

      是不是所有的CPU都会帮你这么做呢?当然不是。有些厂商的CPU发现你访问unaligned address,就会报错,或者打开调试器或者dump core,比如sun sparc solaris绝对不会容忍你访问unaligned address,都会以一个core结束你的程序的执行。所以一般编译器都会在编译时做相应的优化以保证程序运行时所有数据都是存储在'aligned address'上的,这就是内存对齐的由来。

 

 

      原文链接:http://bigwhite.blogbus.com/logs/3990258.html

原创粉丝点击