1.经过内存对⻬之后,CPU 的内存访问速度⼤⼤提升。因为 CPU 把内存当成是⼀块⼀块的,块的⼤⼩可以是 2,4,8,16 个字节,因此 CPU 在读取内存的时候是⼀块⼀块进⾏读取的,块的大小称为内存读取粒度。⽐如说 CPU 要读取⼀个 4 个字节的数据到寄存器中(假设内存读取粒度是 4),如果数据是从 0 字节开始的,那么直接将 0-3 四个字节完全读取到寄存器中进⾏处理即可。
如果数据是从 1 字节开始的,就⾸先要将前 4 个字节读取到寄存器,并再次读取 4-7 个字节数据进⼊寄存器,接着把 0 字节,5,6,7 字节的数据剔除,最后合并 1,2,3,4字节的数据进⼊寄存器,所以说,当内存没有对⻬时,寄存器进⾏了很多额外的操作,⼤⼤降低了 CPU 的性能。
2.有的 CPU 遇到未进⾏内存对⻬的处理直接拒绝处理,不是所有的硬件平台都能访问任意地址上的任意数据,某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。所以内存对齐还有利于平台移植。
首先了解两个概念:
1.每个特定平台上的编译器都有自己的默认“对齐系数”(也叫对齐模数)。32位系统,gcc中默认#pragma pack(4),可以通过预编译命令#pragma pack(n),n = 1,2,4,8,16来改变这一系数。
2.有效对齐值:是给定值#pragma pack(n)和结构体中最长数据类型长度中较小的那个。有效对齐值也叫对齐单位。
然后明确内存对齐的两个规则:
1.结构体第一个成员的偏移量(offset)为0,以后每个成员相对于结构体首地址的 offset 都是该成员大小与有效对齐值中较小那个的整数倍,如有需要编译器会在成员之间加上填充字节。
2.结构体的总大小为 有效对齐值 的整数倍,如有需要编译器会在最末一个成员之后加上填充字节。
看下面这个例子:
#include
using namespace std;
struct X1
{
int i;//4个字节
char c1;//1个字节
char c2;//1个字节
};
struct X2
{
char c1;//1个字节
int i;//4个字节
char c2;//1个字节
};
struct X3
{
char c1;//1个字节
char c2;//1个字节
int i;//4个字节
};
int main()
{
cout<<"long "<
程序的运行结果为:
long 4
float 4
int 4
char 1
x1 的大小 8
x2 的大小 12
x3 的大小 8
在上述程序中,有效对齐值均为默认的4字节。
根据规则1可知,第一个变量为int型,占4字节,从offset为0的位置开始存,占据offset 0 ~ 3的位置。
第二个变量为char型,占1字节,其对于结构体首地址的 offset 是该成员大小与有效对齐值中较小那个的整数倍,即1的整数倍,因此可以直接接在变量i后存放,占据offset 4 的位置。
同理,第三个变量为char型,可以直接接在第二个char型变量后存放,占据offset 5 的位置。
最后,由规则2可知:结构体的总大小为 有效对齐值 的整数倍。在上述程序中,有效对齐值为4,因此最终的结构体大小应为4的整数倍。目前三个变量占 4 + 1 + 1 = 6字节,编译器在最后填充3字节,最终变成8字节。
第一个变量为char型,占1字节,从offset为0的位置开始存,占据offset 0 的位置。
第二个变量为int型,占4字节,由规则1可知,除第一个成员外,结构体的每个成员相对于结构体首地址的 offset 都是该成员大小与有效对齐值中较小那个的整数倍。因此第二个变量只能存放在4的整数倍的位置上。编译器会在第一个char变量后填充3字节,然后把第二个变量从offset为4的位置开始存,占据offset 4 ~ 7的位置。
第三个变量是char型,占1字节,可以直接在第二个变量后面存放,占据offset 8 的位置。
最后,目前三个变量占据 4 + 4 + 1 = 9字节。有效对齐值为4,所以编译器在最后填充3字节,最终变成12字节。
第一个变量为char型,占1字节,从offset为0的位置开始存,占据offset 0 的位置。
第二个变量是char型,占1字节,可以直接在第一个变量后面存放,占据offset 1 的位置。
第三个变量为int型,占4字节,必须存放在offset为4的整数倍的位置上,于是编译器在第二个char变量后填充2字节,使得offset = 4,把int型变量存放在offset 4 ~ 7的位置。
最后,三个变量占据 4 + 4 = 8字节,有效对齐值为4,满足倍数关系,编译器无需填充字节,最终即为8字节。
内存是一个连续的块,我们可以用下面的图来表示, 在32位系统中,它是以4个字节对一个对齐单位的:
让我们看看三个结构在内存中的布局:
首先是 X1,如下图所示
X1 中第一个是 int类型,它占有4字节,所以前面4格就是满了,然后第二个是char类型,这中类型只占一个字节,所以它占有了第二个4字节组块中的第一格,第三个也是char类型,所以它也占用一个字节,它就排在了第二个组块的第二格,因为它们加在一起大小也不超过一个块,所以他们三个变量在内存中的结构就是这样的,因为有内存分块对齐,所以最后出来的结果是8,而不是6,因为后面两个格子其实也算是被用了。
再看X2,如图所示
X2中第一个类型是char类型,它占用一个字节,所以它首先排在第一组块的第一个格子里面,第二个是int类型,它占用4个字节,第一组块已经用掉一格,还剩3格,肯定是无法放下第二int类型的,因为要考虑到对齐,所以不得不把它放到第二个组块,第三个类型是char类型,跟第一个类似。所因为有内存分块对齐,我们的内存就不是8个格子了,而是12个了。
再再看X3,如下图所示
关于X3的说明其实跟X1是类似的,只不过它把两个1个字节的放到了前面,相信看了前面两种情况的说明这里也是很容易理解的。
参考文章:C++内存对齐 - 走看看