why对齐?
为了提高寻址效率。
计算机内存是以字节(Byte)为单位划分的,理论上CPU可以访问任意编号的字节,但实际情况并非如此。
以32位的CPU为例,实际寻址的步长为4个字节,也就是只对编号为 4 的倍数的内存寻址,例如 0、4、8、12、1000 等,而不会对编号为 1、3、11、1001 的内存寻址。
对于程序来说,一个变量最好位于一个寻址步长的范围内,这样一次就可以读取到变量的值;如果跨步长存储,就需要读取两次,然后再拼接数据,效率显然降低了。例如一个 int 类型的数据,如果地址为 8,那么很好办,对编号为 8 的内存寻址一次就可以。如果编号为 10,就比较麻烦,CPU需要先对编号为 8 的内存寻址,读取4个字节,得到该数据的前半部分,然后再对编号为 12 的内存寻址,读取4个字节,得到该数据的后半部分,再将这两部分拼接起来,才能取得数据的值。
将一个数据尽量放在一个步长之内,避免跨步长存储,这就是所谓的内存对齐了。
内存对齐规则
以结构体为例。以下来自百度百科。
1)结构体变量的首地址是其最长基本类型成员的整数倍;
2)结构体每个成员相对于结构体首地址的偏移量(offset)都是成员大小的整数倍,如有需要编译器会在成员之间加上填充字节(internal adding);
备注:为结构体的一个成员开辟空间之前,编译器首先检查预开辟空间的首地址相对于结构体首地址的偏移是否是本成员的整数倍,若是,则存放本成员,反之,则在本成员和上一个成员之间填充一定的字节,以达到整数倍的要求,也就是将预开辟空间的首地址后移几个字节。
3)结构体的总大小为结构体最宽基本类型成员大小的整数倍,如有需要,编译器会在最末一个成员之后加上填充字节(trailing padding)。
备注:
a、结构体总大小是包括填充字节,最后一个成员满足上面两条以外,还必须满足第三条,否则就必须在最后填充几个字节以达到本条要求。
b、如果结构体内存在长度大于处理器位数的元素,那么就以处理器的倍数为对齐单位;否则,如果结构体内的元素的长度都小于处理器的倍数的时候,便以结构体里面最长的数据元素为对齐单位。
- 结构体内类型相同的连续元素将在连续的空间内,和数组一样。
举例验证
# include <iostream>
using namespace std;
struct MyStruct
{
char b; //1
int a; //4
char d; //1
};
int main()
{
cout << sizeof MyStruct << endl; // 12
system("pause");
return 0;
}
对于以上程序结果为12。(1—4—1 ——>>12)
而稍微改变顺序(1—1—4 ——>>8)。
大家可以根据以上规则自己体会。
总结
(1)最后的长度一定是结构体中最长元素所占字节的整数倍。
(2)前面的地址必须是后面的地址正数倍,不是就补齐。