问题1:为什么要内存对齐?
- 平台原因:不是所有的平台都能访问到任意地址上的任何数据,如果在特定的地址上找不到数据的话就会抛出硬件异常。
- 性能问题:简单的来说如果没有使用内存对齐的话,相对于内存对齐,CPU要更多次去访问内存才能将数据正确的读出,所以会出现性能上的降低。(甚至有些CPU没有采取内存对齐的话就会罢工)
问题2:内存对齐有什么规则/规律?
- 第一条规则:第一个数据成员一定要放在偏移量(offset)为0的地址。
- 第二条规则:VS下用#progma pack(n)用来设置内存对齐的系数,每次将要对齐的数据成员长度和n来比较,小的那个一个作为标准来对齐。
- 第三条规则:将所有数据成员对齐之后,整个class或者struct也要进行对齐,它的大小必须是这个类中所占字节最大的数据成员的字节的整数倍数 -
问题3:如何来理解这三条规则?
接下来我将使用几个例子来详细解释内存对齐的三条规则是怎么使用的。
例子1:
#include<iostream>
using namespace std;
class A
{
char a;//1个字节
int b;//4个字节
char c;//1个字节
};
int main()
{
cout << sizeof(A) << endl;//输出的结果是12个字节
}
这是内存分配的示意图:
我们来将代码一步一步分析:
class A
{
char a;//char 1个字节大小
//根据第一条规则,第一个数据成员应该放在第一个位置,所以a从0开始
int b;//4个字节
//根据第二条规则:因为在VS下n默认是8,int在32位下为4个字节,4小于8,所以我们要以4来对齐,而不是8。
//要怎么对齐呢?放在a的后一个位置2可以吗?肯定是不行的,因为我们要以4来对齐,所以它能放的位置必须要以4的倍数开头,比如0,4,8。所以它不能放在成员a的后面,否则的话它就是以2开头了,而2并不是4的整数倍(这里是重点!!!)
//所以b的位置放在4开始
char c;//1个字节
//根据第二条规则:char为1个字节,小于8,所以要以1为对齐系数。
//这时候成员c的起始位置必须以1的倍数为开始,所以可以放在成员b的后面。
//c就是从8开始
//使用规则3:
//一直到了这一步,成员的对齐已经完成了,一共占用了9个字符。接下来要将整个class对齐,我们需要将整个class的大小设置为int的整数倍数,所以9%4=1,我们需要扩展为12个字符才完全对齐。
//到这一步就完全完成了,我们可以参照上面的内存图,如果还不清楚的话可以从头再梳理一遍,相信很快就能完全理解这个规则。
};
例子2:
代码:
#include<iostream>
using namespace std;
class B
{
double a;//8个字符
char b;//1个字符
int c;//4个字符
};
int main()
{
cout << sizeof(B) << endl;
}
内存布局:
我们来将代码一步一步分析:
#include<iostream>
using namespace std;
class B
{
double a;//8个字符
//使用第一条规则:
//第一个成员应该从第一个位置开始放置,也就是地址0
char b;//1个字符
//第二条规则:char1个字符,比8小,所以我们以1为对齐系数
//b的起始位置必须是1的倍数,所以紧跟着double的后面存放
int c;//4个字符
//第二条规则:int4个字节小于8,所以以4为对齐系数。
//c的起始位置必须是4的倍数,所以不能跟着b的后面存放,因为9不是4的倍数,所以我们必须以12为起始地址,因为12是4的倍数。
//这时候已经占了16个字符
//第三条规则
//class的大小要是double的整数倍(因为double所占字符最大),因为16个字符已经是8的倍数,所以不需要再进行内存对齐。
//如果有不清楚的,可以从上面的规则开始重新看起。
};
int main()
{
cout << sizeof(B) << endl;
}
总结:用progma pack(n)可以强制设置你想要的内存对齐的比较系数,如果你设置为1的话,就是所有的成员都紧凑的贴在一起,不会有空隙,而如果n设置为16,比所有的数据成员的字符大小都大,那么永远都不会以n为对齐系数。
如果还有不懂的地方,可以交流一下。