为什么写这篇文章呢?一直以来貌似没有关注过这个问题,刷博客时,遇到了,发现自己还真的有点不清晰,故而写下这篇文章来帮助一些人。

 

还是先问几个问题,这样能帮助大家理解内存对齐。

1.内存对齐是什么?

2.为什么需要内存对齐?

3.内存对齐的规则。

 

1.内存对齐是什么?

数据在内存中存储往往不是像我们想象的那样,一个挨着一个毫无空间浪费的顺序排列。为了存取的效率是按一定规则存放的,那么这个规则就是内存对齐。

 

2.为什么需要内存对齐?

虽然内存的基本单位是字节,但是大部分处理器存取数据的往往不是按字节来的,一般会以双字节、4字节、8字节、16字节、甚至32字节为单位来存取。

为什么按照这些字节来存取呢?当然不是凭空就这样的,而是为了存取的效率,

eg:现在考虑4字节存取粒度的处理器取int类型变量(32位系统),该处理器只能从地址为4的倍数的内存开始读取数据。

假如没有内存对齐机制,数据可以任意存放,现在一个int变量存放在从地址1开始的联系四个字节地址中,该处理器去取数据时,要先从0地址开始读取第一个4字节块,剔除不想要的字节(0地址),然后从地址4开始读取下一个4字节块,同样剔除不要的数据(5,6,7地址),最后留下的两块数据合并放入寄存器.这需要做很多工作.

现在有了内存对齐的,int类型数据只能存放在按照对齐规则的内存中,比如说0地址开始的内存。

那么现在该处理器在取数据时一次性就能将数据读出来了,而且不需要做额外的操作,提高了效率。

 

3.内存对齐的规则。

每个特定平台上的编译器都有自己默认的“对齐系数”。gcc中默认#pragma  pack(4),

当然可以通过预编译指令修改对齐系数

#pragma  pack(n)   //n 可以为1、2、4、8、16

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

即(min(n,结构体中最长数据类型长度))

 

内存对齐遵从的规则:

(1) 结构体第一个成员的偏移量(offset)为0,以后每个成员相对于结构体首地址的 offset 都是该成员大小与有效对齐值中较小那个的整数倍,

如有需要编译器会在成员之间加上填充字节。

(2)  结构体的总大小为 有效对齐值 的整数倍,如有需要编译器会在最末一个成员之后加上填充字节。

 

//32位操作系统

#include<stdio.h>
struct
{
    int i;    
    char c1;  
    char c2;  
}x1;

struct{
    char c1;  
    int i;    
    char c2;  
}x2;

struct{
    char c1;  
    char c2; 
    int i;    
}x3;

int main()
{
    printf("%d\n",sizeof(x1));  // 输出8
    printf("%d\n",sizeof(x2));  // 输出12
    printf("%d\n",sizeof(x3));  // 输出8
    return 0;
}

 

 

以上测试都是在Linux环境下进行的,linux下默认#pragma pack(4),且结构体中最长的数据类型为4个字节,

所以有效对齐单位为4字节,下面根据上面所说的规则以x2来分析其内存布局:

 struct{

    char c1;  
    int i;    
    char c2;  
}x2;

首先使用规则1,对成员变量进行对齐:

sizeof(c1) = 1 <= 4(有效对齐位),按照1字节对齐,占用第0单元;

sizeof(i) = 4 <= 4(有效对齐位),相对于结构体首地址的偏移要为4的倍数,占用第4,5,6,7单元;

sizeof(c2) = 1 <= 4(有效对齐位),相对于结构体首地址的偏移要为1的倍数,占用第8单元;

然后使用规则2,对结构体整体进行对齐:

x2中变量i占用内存最大占4字节,而有效对齐单位也为4字节,两者较小值就是4字节。因此整体也是按照4字节对齐。

由规则1得到x2占9个字节,此处再按照规则2进行整体的4字节对齐,所以整个结构体占用12个字节。

 
三个例子的内存布局如下:

 

#pragma pack(n)

不同平台上编译器的 pragma pack 默认值不同。而我们可以通过预编译命令#pragma pack(n), n= 1,2,4,8,16来改变对齐系数。

例如,对于上个例子的三个结构体,如果前面加上#pragma pack(1),那么此时有效对齐值为1字节,此时根据对齐规则,不难看出成员是连续存放的,三个结构体的大小都是6字节。

 

如果前面加上#pragma pack(2),有效对齐值为2字节,此时根据对齐规则,三个结构体的大小应为6,8,6。内存分布图如下

相信看了上面的例子,已经对内存对齐有了比较深的理解,那么以后自己在定义结构体的时候要考虑成员类型的

先后顺序了。

 

其实还有一种情况没有讲到,先保留,有时间我再讲,大家可以先思考。

结构体中成员还是一个结构体

struct A

{

  int a;

  char c;

};

struct B

{

  int b;

  A a;

}

默认对齐是4

嵌套对齐分两部分对齐

一部分是A中对齐,按照A的整体对齐方式

另一部分是B的整体对齐,是在A的整体对齐的基础上再整体对齐,min(4,max(A成员长度,B成员长度))

struct A

{

  int a;  //4

  char c;   //1

};

A整体对齐8

struct B

{

  int b; //4

  A a; //8

}

B整体对齐 12