C/C++中的内存对齐(Memory Alignment)是件很有意思的事,现在分别介绍其产生的原因和具体规则,最后就是举例分析说明。
产生的原因
为什么内存中需要对齐呢?
在CPU眼中,它把内存当成是一块一块的,块的大小可以是2,4,8,16字节大小,它在读取内存时也是一块一块进行读取的。同时不同的数据类型又是按照自己所占的字节数来进行存储的。
假设现在一个int型存储在0-3字节中,当CPU要读取这个int型的数据到寄存器中,就能一次过把这4个字节读到寄存器中。但是当该数据是从1字节开始存储的,问题变的有些复杂。CPU要先读取0—3字节的数据进寄存器,并再次读取4—7字节的数据进寄存器,接着把0字节和6,7字节的数据剔除,最后合并1,2,3,4字节的数据进寄存器。对一个内存未对齐的数据进行了这么多额外的操作,大大降低了CPU性能。即使内存在对齐数据之后,可能会使得占用的多余空间变多,经过内存对齐后,CPU的内存访问速度大大提升,这样的牺牲也是值得的。
具体规则
(1)数据成员对齐规则:结构(struct)(或联合(union))的数据成员,第一个数据成员放在offset为0的地方,以后每个数据成员的对齐按照#pragma pack指定的数值和这个数据成员自身长度中,比较小的那个进行。即d=min(#pragma pack(n),这个数据成员自身长度)
(2)结构(或联合)的整体对齐规则:在数据成员完成各自对齐之后,结构(或联合)本身也要进行对齐,对齐将按照#pragma pack指定的数值和结构(或联合)最大数据成员长度中,比较小的那个进行。即d=min(#pragma pack(n),最大数据成员长度)
结合(1)、(2)推断:
(3)当#pragma pack的n值等于或超过所有数据成员长度的时候,这个n值的大小将不产生任何效果。
(4)各成员变量存放的起始地址相对于结构的起始地址的偏移量必须为该变量的类型所占用的字节数的倍数。
(5)各成员变量在存放的时候根据在结构中出现的顺序依次申请空间,同时按照上面的对齐方式调整位置,空缺的字节自动填充。
(6)同时为了确保结构的大小为结构的字节边界数(即该结构中占用最大空间的类型所占用的字节数)的倍数,所以在为最后一个成员变量申请空间后,还会根据需要自动填充空缺的字节。结构的总大小必须为占用空间最大的变量占用的空间数的倍数,否则必须为n的倍数;
VC中提供了#pragma pack(n)宏来设定变量以n字节对齐方式。
举例说明
这里就举较为常见的计算结构体(联合体)所占字节的例子。
1 #include<stdio.h>
#pragma pack(4) 表示设置为4字节对齐。
2 struct test
3 {
4 char a;
5 int b;
6 char c;
7 static int d
8 double e;
9 char f;
10 };
11
12 int main(void)
13 {
14 printf("%d ",sizeof(struct test));
15 return 1;
16 }
分析
char a; 长度1,起始offset=0,a存放地址为[0]
int b; 长度4,min(4,4)按4对齐;起始offset=1, 由规则4可知,a补齐后面三位[1,3],b存放位置区间[4,7]
char c; 长度1,min(4,1)按1对齐;起始offset=8, b不用补齐,c存放区间[8]
static int d; 因为静态变量是存放在全局数据区,而sizeof是计算栈中的字节数,因此d不会计算在内
double e; 长度8,min(4,8)按4对齐;起始offset=12, 由规则4可知,c补齐后面三位[9-11];e存放区间[12,19]
char f; 长度1,min(4,1)按1对齐;起始offset=20, e不用补齐,f存放区间[20]
经过上面的分析test的成员共占用内存区间[0,20],大小为21个字节,然后进行整体对齐,需要满足整体为min(4,8)=4的倍数,那么最接近的就是24,即f后面要多补三位[21,23],所以结构体test占用的内存空间为[0,23],一共24字节。
此时全部数据在内存中实际分配到的字节情况为:(0为实际位,x为补齐位)
0xxx | 0000 | 0xxx | 00000000 | 0xxx |
a | b | c | e | f |
优化结构体的定义
从以上分析得知,在定义一个结构体时,数据最好是按照所占字节数从小到大的顺序来写。
1 struct test2
2 {
3 char a;
4 char c;
5 char f;
6 int b;
7 static int d
8 double e;
9 };
而新的定义的结果为16=(1+1+2+4+8)。
此时全部数据在内存中实际分配到的字节情况为:(0为实际位,x为补齐位)
0 | 0 | 0x | 0000 | 00000000 |
a | c | f | b | e |