最近复习时,看到内存对齐,于是对这一块研究了下,记录下来。
(1)内存对齐概念
一个变量在内存中的地址是其长度的整数倍,称之为自然对齐。比如对一个int型变量,其地址是0x00000004,表示该变量是自然对齐的。
(2)为什么需要内存对齐
主要是提高cpu访问内存的速度。如果不对数据进行对齐,cpu读取数据可能需要两次或者更多次,然后在合并成需要的数据类型,这样效率太低了。
例如:对一个int类型变量
(a)地址为0x00000004,此时是自然对齐,cpu只需要一次就完成了读取。
(b)地址为0x00000002,此时数据没有对齐,cpu先读取一个short,然后在读取一个short,然后组合成一个int类型数据,用了两次。
(c)地址为0x00000003,此时数据没有对齐,cpu先读取一个char,然后读取一个short,然后在读取一个char,然后组合成int类型,用了三次。
可见数据对齐对提高cpu访问数据的效率很有必要。
(3)内存对齐规则
有四个基本概念:
(a)数据类型自身对齐值
char类型自身对齐值1个字节,short类型为2,int,float为4,double为8。
(b)结构体或者类自身对齐值
成员自身对齐值的最大值
(3)指定对齐值
#define pack (n),表示按n个字节对齐。
(4)数据成员、结构体和类的有效对齐值
自身对齐值和指定对齐值中的较小值。
有效对齐值N决定了最终数据存放地址的值,最终要的。有效对齐到N,表示数据地址对N求余为0,即"数据地址%N = 0"。
对齐的三条规则:
(a)成员对齐:结构体个成员的其实地址必须是成员数据类型长度的整数倍。
(b)结构体整体需求:结构体总的长度必须是成员最大数据类型长度的整数倍。
(c)编译器对齐指令:vc中#pragma pack(n),按照n字节进行对齐。
例子如下:
1、没有用编译器对齐指令#pragma pack(n)
struct a
{
char ax;
int bx;
short cx;
};
ax的其实地址为0x00000000,自身对齐值为1,0x00000000%1=0,第一个字节存ax。而int类型自身对齐值为4,0x00000001%4!=0,0x00000002%4!=0,0x00000003%4!=0,0x00000004%4=0,所以bx存储的其实地址为0x00000004,还有后续的连续三个字节,0x00000005,0x00000006,0x00000007。cx的自身对齐值为2,其实地址0x00000008%2=0,后续占用一个字节0x00000009;按照这样算来,struct
a应该占用10个字节,亲,别忘了还要考虑整体需求。结构中成员最大数据类型为int,总的字节数应该是int长度的整数倍,所以short后还需要补两个字节,共12个字节。即sizeof(a)=12。
2、使用编译器对齐指令#pragma pack(n)
分两种情况讨论:
(a)n比结构体中最大数据类型大,此时采用默认对齐方式
#pragma pack (8)
struct a
{
char ax;
int bx;
short cx;
};
此时,struct a的长度12,sizeof(a)=12。
(b)n比结构体中最大数据类型大,此时按照n进行对齐
#pragma pack (2)
struct a
{
char ax;
int bx;
short cx;
};
ax占用0x00000000,0x00000001两个字节,bx占用0x000000002,x000000003,x000000004,x00000005四个字节,cx占用0x000000006,x00000007两个字节。故struct a共占用8个字节,sizeof(a)=8。