一、定义      

       现代计算机中内存空间都是按照byte划分的,从理论上讲似乎对任何类型的变量的访问可以从任何地址开始,但实际上,为了减少CPU读取内存的此时,在访问特定类型变量的时候经常在特定的内存地址访问,这就需要各种类型数据按照一定的规则在空间上排列,而不是顺序的一个接一个的排放,这就是对齐。

———————————————————————————————————————————————————————————— 

二、原理解释   

    下面这段转载自:http://www.cnblogs.com/wuzhenbo/archive/2012/06/05/2537465.html

    在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'上的,这就是内存对齐的由来。

我们可以指定按照何种粒度访问特定内存块儿:其中void *T为指向特定内存块的地址指针 
char *p = (char*)T;每次操作一个字节 
short *p = (short*)T;每次操作两个字节 
int *p = (int*)T;每次操作四个字节 
以此类推。

      在'Data alignment: Straighten up and fly right'这篇文章中作者还得出一个结论那就是:"如果访问的地址是unaligned的,那么采用大粒度访问内存有可能比小粒度访问内存还要慢"。

———————————————————————————————————————————————————————————

三、数据类型自身的对齐值

1.对于char型数据,其自身对齐值为1,对于short型为2,对于int,float,long类型,其自身对齐值为4,对于double,其自身对齐值为8。

2.结构体或者类的自身对齐值:其成员中自身对齐值最大的那个值。

3.指定对齐值:#pragma pack(value)时的指定对齐值value。

4.数据成员、结构体和类的有效对齐值:自身对齐值和 指定对齐值中小的那个值。

      有了这些值,我们就可以很方便的来讨论具体数据结构的成员和其自身的对齐方式。有效对齐值N是最终用来决定数据存放地址方式的值,有效对齐值N,就是表示“对齐在N上”,也就是说该数据的"存放起始地址%N=0". 而数据结构中的数据变量都是按定义的先后顺序来排放的。第一个数据变量的起始地址就是数据结构的起始地址。 结构体的成员变量要对齐排放,结构体本身也要根据自身的有效对齐值圆整( 就是结构体成员变量占用总长度需要是结构体有效对齐值的整数倍)。

四、例子

 1、

    struct B 
    { 
       char b; 
       int a; 
       short c; 
    }d; 

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

输出:12

解释:struct B中的数据成员中最大自身对齐值为4(即int的对齐值 ),所以struct B的自身对齐值为4,假设b的地址为0x00000004,则char b的地址为0x00000004。int a的自身对齐值为4,所以int a的地址为0x00000008。short c的地址是0x0000000c,而short c需要两个字节就够啦,所以我们觉得sizeof(d)应该是10。这里是为了数组而这样设计的,假设有一个struct B的数组,它的第一个元素肯定是符合对齐要求的,那么第二个数组元素呢?如果sizeof(d)是我们以为的10的话,就不符合对齐要求啦,那么第二个数组元素就要与第一个数组元素隔开两个字节,这与数组元素是连续分配的相矛盾。所以sizeof(d)是12的话,就满足要求啦。也就是这句话:结构体的成员变量要对齐排放,结构体本身也要根据自身的有效对齐值圆整( 就是结构体成员变量占用总长度需要是结构体有效对齐值的整数倍)。