struct/class/union内存对齐原则有四个:
1).数据成员对齐规则:结构(struct)(或联合(union))的数据成员,第一个数据成员放在offset为0的地方,以后每个数据成员存储的起始位置要从该成员大小或者成员的子成员大小(只要该成员有子成员,比如说是数组,结构体等)的整数倍开始(比如int在32位机为4字节, 则要从4的整数倍地址开始存储),基本类型不包括struct/class/uinon。
2).结构体作为成员:如果一个结构里有某些结构体成员,则结构体成员要从其内部"最宽基本类型成员"的整数倍地址开始存储.(struct a里存有struct b,b里有char,int ,double等元素,那b应该从8的整数倍开始存储.)。
3).收尾工作:结构体的总大小,也就是sizeof的结果,.必须是其内部最大成员的"最宽基本类型成员"的整数倍.不足的要补齐.(基本类型不包括struct/class/uinon)。
4).sizeof(union),以结构里面size最大元素为union的size,因为在某一时刻,union只有一个成员真正存储于该地址。
实例解释:下面以class为代表
No.1
class Data
{
char c;
int a;
};
cout << sizeof(Data) << endl;
No.2
class Data
{
char c;
double a;
};
cout << sizeof(Data) << endl;
No.3
class Data
{
char c;
int a;
char d;
};
cout << sizeof(Data) << endl;
No.4
class Data
{
char c;
char d;
int a;
};
cout << sizeof(Data) << endl;
No.3运行结果为 12 No.4运行结果为 8
class中的数据成员放入内存的时候,内存拿出一个内存块来,数据成员们排队一个一个往里放,遇到太大的,不是把自己劈成两半,能放多少放多少,而是等下一个内存块过来。这样的话,就可以理解为什么No.3,No.4两端的代码输出结果不一样了,因为左边是1+(3)+4+1+(3)=12,而右边是1+1+(2)+4=8。括号中为补齐的bytes。
No.5
class BigData
{
char array[33];
};
class Data
{
BigData bd;
int integer;
double d;
};
cout << sizeof(BigData) << " " << sizeof(Data) << endl;
No.6
class BigData
{
char array[33];
};
class Data
{
BigData bd;
double d;
};
cout << sizeof(BigData) << " " << sizeof(Data) << endl;
No.5和No.6运行结果均为: 48
在默认条件下,内存对齐是以class中最大的那个基本类型为基准的,如果class中有自定义类型,则递归的取其中最大的基本类型来参与比较。在No.5和No.6中内存块一个接一个的过来接走数据成员,一直到第5块的时候,BigData里只剩1个char了,将它放入内存块中,内存块还剩7个bytes,接下来是个int(4bytes),能够放下,所以也进入第5个内存块,这时候内存块还剩3bytes,而接下来是个double(8bytes),放不下,所以要等下一个内存快到来。因此,No.5的Data的size=33+4+(3)+8=48,同理No.6应该是33+(7)+8=48。
顺便提一下Union: 共用体表示几个变量共用一个内存位置,在不同的时间保存不同的数据类型和不同长度的变量。在union中,所有的共用体成员共用一个空间,并且同一时间只能储存其中一个成员变量的值。
内存对齐的主要作用是:
1、平台原因(移植原因)
A 不是所有的硬件平台都能访问任意地址上的任意数据的;
B 某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
2、性能原因:
A 数据结构(尤其是栈)应该尽可能地在自然边界上对齐。
B 为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。
CPU执行指令时从内存中获取数据是按cache line操作的,一般为64字节。
假设cache line为4字节,上图中如果CPU读取4-bytes或以上大小的数据的起始地址为1,则需要至少读取2个数据块,然后把2个块中不需要的数据抛弃,再将有用数据拼接成4-bytes的数据,这明显增加了CPU的操作,影响了CPU指令的执行效率。如果CPU读取数据的起始地址在0,4,则一次只需要读一个数据块,CPU的读取指令就是一次原子操作。