1:什么是内存对齐
首先看一个小例子:
struct {
int x;
char y;
} s1;
int main(int argc, const char * argv[]) {
printf("%lu\n", sizeof(s1));
}
输出为:8
在Mac系统中 int占4个byte, char占1个byte, 那么把它们放在结构体里应该站在4 + 1 = 5byte. 但是结构是8byte, 这就是由于字节对齐导致的.
现代计算机中内存空间都是按照 byte 划分的,从理论上讲似乎对任何类型的变量的访问可以从任何地址开始,但是实际的计算机系统对基本类型数据在内存中存放的位置有限制,它们会要求这些数据的首地址的值是某个数k(通常它为4或8)的倍数,这就是所谓的内存对齐。
2:为什么要内存对齐
为了内存获取速度更快
实际内存读取的时候,是内存单元每n个为一组,一次读一组, n称为对齐系数.
每个特定平台上的编译器都有自己的默认“对齐系数”(也叫对齐模数)。程序员可以通过预编译命令#pragma pack(n),n=1,2,4,8,16来改变这一系数,其中的n就是你要指定的“对齐系数”。
拿上面的结构体为例, 假设n=4的时候
1, 2, 3, 4存的是int类型的x, 5号内存存的是char y.
如下体所示
- 读x的时候只需要拿出第一组1到4号内存, 读出x;
- 读y的时候拿出5到8号内存, 然后扣出5号内存读出来;
那么如果n=2的时候,会发生什么呢,如图所示:
读x的时候就要先读出1,2号内存, 再读出3,4号存, 然后再拼到一起. 这样就会造成读了两次内存,对于CPU来说,内存访问速度很慢,访问两次就会浪费很多时间.
3:对齐的规则
-
数据成员对齐规则:(
Struct或者Union的数据成员)第一个数据成员放在偏移为0的位置。以后每个数据成员的位置为min(对齐系数,自身长度)的整数倍,下个位置不为本数据成员的整数倍位置的自动补齐。 -
数据成员为结构体:该数据成员的内最大长度的整数倍的位置开始存储。
-
整体对齐规则:数据成员按照1,2步骤对齐之后,其自身也要对齐,对齐原则是min(对齐系数,数据成员最大长度)的整数倍。
struct {
double a;
char b;
int c;
short d;
} s1;
struct {
double a;
int c;
char b;
short d;
} s2;
int main(int argc, const char* argv[]) {
printf("%lu\n", sizeof(s1));
printf("%lu\n", sizeof(s2));
}
输出: s1为24, s2为16
s1的内存结构:
- a 是
double类型, 占8个字节的空间,偏移量为0 - b 是
char类型, 占1个字节空间, 按照规则,b 的偏移量是char大小的整数倍,当前的偏移量是8,是其偏移量的整数倍, b紧跟在a的后面 - c 是
int类型,占4个字节的空间,根据规则1, 4的偏移量必须是int的整数倍,所以编译器会在b后面插入三个字节缓冲区 - d是
short类型, 占2个字节空间, d的偏移量正好是16个字节 - 此时s1 大小为18个字节, 根据规则3, 结构体s1的大小必须是其最大成员double的整数倍, 所以最后s1所占内存大小事18+6 =
24