1
2
3
4
5
6
7
|
struct
db
{
char
c1;
int
i1;
char
c2;
float
f;
};
|
结构的第一个成员c1,其偏移地址为0,占据了第1个字节。第二个成员i1为int类型,其起始地址必须4字节对界,因此,编译器在c1和i1之间填充了3个空字节。结构的第三个成员c2恰好落在其自然对界地址上,在它们前面不需要额外的填充字节,但是c2和f之间必须再填充3个空字节,因为第四个成员f必须对其4字节地指处,因此整个db结构占用(1+3)+ 4 + (1+3) +4 = 16字节。现在大概明白了大致的对其方式。
1
2
3
4
5
6
|
struct
db
{
char
c1;
int
i1;
char
c2;
};
|
1
2
3
4
5
6
|
struct
db
{
char
c1;
char
c2;
char
c3;
};
|
1
2
3
4
5
6
7
|
struct
db
{
char
c1;
int
i1;
float
f;
double
d1;
};
|
1)平台原因(移植原因):不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
2)性能原因:经过内存对齐后,CPU的内存访问速度大大提升。具体原因稍后解释。
看下图:
图一:
这是普通程序员心目中的内存印象,由一个个的字节组成,而CPU并不是这么看待的。
图二:
CPU把内存当成是一块一块的,块的大小可以是2,4,8,16字节大小,因此CPU在读取内存时是一块一块进行读取的。块大小成为memory accessgranularity(粒度)
假设CPU要读取一个int型4字节大小的数据到寄存器中,分两种情况讨论:
1、数据从0字节开始
2、数据从1字节开始
再次假设内存读取粒度为4。
图三:
当该数据是从0字节开始时,很CPU只需读取内存一次即可把这4字节的数据完全读取到寄存器中。
当该数据是从1字节开始时,问题变的有些复杂,此时该int型数据不是位于内存读取边界上,这就是一类内存未对齐的数据。
图四:
此时CPU先访问一次内存,读取0—3字节的数据进寄存器,并再次读取4—5字节的数据进寄存器,接着把0字节和6,7,8字节的数据剔除,最后合并1,2,3,4字节的数据进寄存器。对一个内存未对齐的数据进行了这么多额外的操作,大大降低了CPU性能。
这还属于乐观情况了,上文提到内存对齐的作用之一为平台的移植原因,因为以上操作只有有部分CPU肯干,其他一部分CPU遇到未对齐边界就直接罢工了。