内存对齐
内存对齐
一、内存对齐
所谓内存对齐,是指不同类型的数据按照一定的规则,进行存储的方式。
对齐原因:
1、平台原因(移植原因):不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据(内存对齐),否则抛出硬件异常。
2、性能原因:数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问(奇偶地址);而对齐的内存访问仅需要一次访问
二、对齐方法
struct Struct1
{
double data1;
int data2;
char data3;
};
对Struct1进行sizeof()求长度,得出的是为16,而非:
sizeof(Struct1)=sizeof(double)+sizeof(int)+sizeof(char)=8+4+1=13;
这是因为编译器对变量的偏移(offset)地址做了对齐处理。处理步骤如下:
struct Struct1
{
double data1; //从offset 0 处开始存储,存储地址空间为offset[0, 7]
int data2; // offset 8 % sizeof(int) = 8 / 4 = 0; 则从 offset 8处开始存储,存储地址空间为offset[8, 11]
char data3; // offset 12 % sizeof(char) = 12 / 1 = 0; 则从offset 12处开始存储,存储地址空间为offset 12
};
上面总共使用了存储13个bytes的空间,接下来还要对struct1自身进行内存对齐,对齐方法是:补齐当前的空间长度,使其能够整除struct中数据长度最长的数据类型;对于上述的Struct1,最长的数据类型是double,长度为8,那么Struct1的size必须为8的整数倍,13是不符的,所以Struct1现有13个bytes 的基础上补5被byte,即16。 Struct1的内存布局如下:
D1 |
D2 |
D3 |
D4 |
D5 |
D6 |
D7 |
D8 |
I1 |
I2 |
I3 |
I4 |
C1 |
Null |
Null |
Null |
通过上面的例子,可以知道对齐的规则是:
① 变量存放的起始地址相对于数据结构的起始地址的偏移量(offset),必须是当前数据类型长度的整数倍。
② 数据结构的长度必须是数据结构中数据长度最大的数据类型的长度的整数倍
根据上面的例子:
struct Struct2
{
char data1;
double data2;
char data3;
};
可以得出Struct2的长度为24。Struct2的内存布局如下:
C1 |
|
|
|
|
|
|
|
D1 |
D2 |
D3 |
D4 |
D5 |
D6 |
D7 |
D8 |
C2 |
|
|
|
|
|
|
|
其中空的部分为Null
三,自定义对齐字节数
定义最大字节:
#pragma pack(n) // n = 1, 2, 4, 8, 16…
…
… // 自定义作用域
…
#pragma pack() // 结束自定义对齐,恢复默认
n和数据结构中的数据类型长度做比较,取较小的为有效数值。
#pragma pack(2)
struct Struct3
{
char data1; // offset 0
double data2; // sizeof(double)>2, 2有效, offset[1,9]
int data3; // sizeof(int)>2, 2有效, offset[10,11]
};
#pragma pack()
Sturct3的数据长度为12
定义最小字节:
__declspec( align(n) )struct Struct4
{
char data1;
double data2;
int data3;
};
__declspec( align(n) )优先级高于#pragma pack()。
在__declspec( align () )之前,数据按照#pragma pack规定的方式存储。
遇到__declspec( align())时,首先寻找距离当前偏移向后最近的对齐点(满足对齐长度为max(数据自身长度,指定值)),然后把数据从这个对齐点开始存储;后面的数据类型,仍按照#pragma pack的规定存储。
当所有数据存储完毕,把结构的整体对齐数值和__declspec( align())规定的数值进行比较,取较大的数值作为整个结构的对齐长度。
当__declspec( align() )指定的数值小于对应类型长度时,指定无作用。