内存对齐相关问题详解

目录:

内存对齐问题是笔试、面试中一个非常重要的问题,不仅在笔试、面试中很重要,它对于我们理解数据在内存中的存储也有很深刻的意义,所以在这里进行一次总结。

什么是内存对齐

我们现在使用的算机中内存空间都是按照字节(Byte)划分的,理论上说,似乎对任何类型的变量的访问可以从任意地址开始,但实际情况则是在访问特定类型变量的时候经常在特定的内存地址访问,这就需要各种类型的数据按照一定的规则在内存空间上排列,而不是顺序的一个接一个地排放,这就是对齐。

默认对齐数

Linux 默认#pragma pack(4)
window 默认#pragma pack(8)
注:可以通过预编译命令#pragma pack(n) ,n=1,2,4,8,16来改变这一系数,其中的n就是指定的“对齐系数”。

内存对齐原则

1.第一个成员在与结构体变量地址偏移量为0的地址处(即第一个成员存放在结构体的首地址处);
2.其它成员变量要对齐到某个数字(对齐数)的整数倍地址处。对齐数=编译器默认的对齐数与该成员大小的较小值(我们会在后面详细讲解);
3.结构体总大小为最大对齐数的整数倍(通过第二条我们知道,结构体中每一个成员都有一个自己对应的对齐数);
4.如果有嵌套结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(包含嵌套结构体)的整数倍。

为什么存在内存对齐

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

总的来说,结构体内存对齐就是拿空间换取时间的做法。

内存对齐的实例详细解析

例一:一字节对齐

第一步: 成员数据对齐

#pragma pack(1)//指定编译器对齐数为1
struct AA {
 int a;     //长度4 < 1 **按1对齐**;偏移量为0;存放位置区间[0,3]
 char b;    //长度1 = 1 **按1对齐**;偏移量为4;存放位置区间[4]
 short c;   //长度2 > 1 **按1对齐**;偏移量为5;存放位置区间[5,6]
 char d;    //长度1 = 1 **按1对齐**;偏移量为6;存放位置区间[7]
 //整体存放在[0~7]位置区间中,共八个字节。
};

第二步: 整体对齐
整体对齐系数 = 1(每个数据成员对齐数分别为1 1 1 1),所以不需要再进行整体对齐。整体大小就为8。
图示如下:

例二:二字节对齐

第一步: 成员数据对齐

#pragma pack(2)
struct AA {
 int a;     //长度4 > 2 **按2对齐**;偏移量为0;存放位置区间[0,3]
 char b;    //长度1 < 2 **按1对齐**;偏移量为4;存放位置区间[4]
 short c;   //长度2 = 2 **按2对齐**;偏移量要提升到2的倍数6;存放位置区间[6,7]
 char d;    //长度1 < 2 **按1对齐**;偏移量为7;存放位置区间[8];共九个字节
};

第二步: 整体对齐
整体对齐系数 = 2(每个数据成员的对齐数分别为2 1 2 1,最大对齐数为2),将9提升到2的倍数,则为10.所以最终结果为10个字节。

例三:四字节对齐

第一步: 成员数据对齐

#pragma pack(4)//指定编译器4字节对齐
struct AA {
 int a;     //长度4 = 4 **按4对齐**;偏移量为0;存放位置区间[0,3]
 char b;    //长度1 < 4 **按1对齐**;偏移量为4;存放位置区间[4]
 short c;   //长度2 < 4 **按2对齐**;偏移量要提升到2的倍数6;存放位置区间[6,7]
 char d;    //长度1 < 4 **按1对齐**;偏移量为7;存放位置区间[8];总大小为9
};

第二步: 整体对齐
整体对齐系数 = 4(每个成员对应的对齐数分别为:4 1 2 1,最大对齐数为4),将9提升到4的倍数,则为12.所以最终结果为12个字节。

例四:八字节对齐

第一步: 成员数据对齐

#pragma pack(8)//指定编译器8字节对齐
struct AA {
 int a;     //长度4 < 8 **按4对齐**;偏移量为0;存放位置区间[0,3]
 char b;    //长度1 < 8 **按1对齐**;偏移量为4;存放位置区间[4]
 short c;   //长度2 < 8 **按2对齐**;偏移量要提升到2的倍数6;存放位置区间[6,7]
 char d;    //长度1 < 8 **按1对齐**;偏移量为7;存放位置区间[8],总大小为9
};

第二步: 整体对齐
整体对齐系数 = 4(每个成员的对齐数分别为:4 1 2 1,最大对齐数为4),将9提升到4的倍数,则为12.所以最终结果为12个字节。

例五:结构体嵌套的内存对齐1

第一步:数据成员对齐

#pragma pack(8)//指定编译器8字节对齐
struct EE
{
 int a;     //长度4 < 8 **按4对齐**;偏移量为0;存放位置区间[0,3]
 char b;    //长度1 < 8 **按1对齐**;偏移量为4;存放位置区间[4]
 short c;   //长度2 < 8 **按2对齐**;偏移量由5提升到6;存放位置区间[6,7]
 //结构体struct FF内部最大元素为int,由于偏移量为8刚好是4的整数倍,所以从8开始存放接下来的struct FF
 struct FF
 {
 int a1;    //长度4 < 8 **按4对齐**;偏移量为8;存放位置区间[8,11]
 char b1;   //长度1 < 8 **按1对齐**;偏移量为12;存放位置区间[12]
 short c1;  //长度2 < 8 **按2对齐**;偏移量为13,提升到2的倍数14;存放位置区间[14,15]
 char d1;   //长度1 < 8 **按1对齐**;偏移量为16;存放位置区间[16]
 };
 //struct FF整体对齐系数 = 4(struct FF中成员的对齐数分别为:4 1 2 1,最大对齐数为4)
 char d;    //长度1 < 8 **按1对齐**;偏移量为21;存放位置区间[21]
};

第二步:整体对齐
整体对齐系数 = 4(数据成员对齐数分别为 4 1 2 4(结构体中嵌套结构体的最大对齐数) 1,最大对齐数为4),将内存大小由21补齐到4的整数倍24

例六:结构体嵌套的内存对齐2

第一步:成员对其

#pragma pack(8)//指定编译器8字节对齐
struct B {
 char e[2]; //长度1 < 8 **按2对齐**;偏移量为0;存放位置区间[0,1]
 short h;   //长度2 < 8 **按2对齐**;偏移量为2;存放位置区间[2,3]
 //结构体 struct A 内部最大元素为double,此刻偏移量为4,提升到8,所以从8开始存放接下来的struct A
 struct A {
 int a;     //长度4 < 8 **按4对齐**;偏移量为8;存放位置区间[8,11]
 double b;  //长度8 = 8 **按8对齐**;偏移量为12,提升到16;存放位置区间16,23]
 float c;   //长度4 < 8,**按4对齐**;偏移量为24,存放位置区间[24,27]
 };
};

第二步:整体对齐
整体对齐系数 = 8(结构体成员对齐数分别为:2 2 8(struct A的最大对齐数),最大对齐数为8),将内存大小由28补齐到8的整数倍32。

猜你喜欢

转载自blog.csdn.net/lws123253/article/details/80302645