Memory Alignment

关于内存对齐的一点探讨

为什么需要内存对齐

内存对齐是一种提高内存访问速度的策略,cpu在访问未对齐的内存需要经过两次内存访问,而经过内存对齐一次就可以了

计算机在读取内存的时候,是一块一块的读取的

1
2
3
4
我们认为是一个一个的读取的
|1|2|3|4|5|6|7|8|
但cpu是一块一块地读取的,也就是先读1234,再读5678
|1|2|3|4|------|5|6|7|8|

那为什么需要内存对齐?因为如果你不理解内存对齐,有可能会导致

  • 程序运行速度变慢
  • 应用程序产生死锁
  • 操作系统崩溃
  • 你的程序会毫无征兆的出错,产生错误的结果

首先这里只关注第一个(其余3个都与平台和硬件相关),程序运行速度变慢?why?

因为如果存在以下的结构体

1
2
3
4
5
struct Test {
char c1; //1
int i; //4
char c1; //1
}

Test的内存结构是这样的

1
2
|c1|i|i|i|
|i|c2| | |

那么为了读取i,cpu要读取两次并将其拼接,才能够得到i的值,为了更高的效率

可以这样摆放

1
2
3
|c1| | | |
|i|i|i|i|
|c2| | | |

这样的话就能一次性将i读取出来

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#program pack(4)
struct Test {
char c1;
int i;
char c2;
};

int main() {
Test v;
printf("%p\n", &v.c1);
printf("%p\n", &v.i);
printf("%p\n", &v.c2);
return 0;
}
//可以看出编译器已经进行了内存对齐
0x7ffeee79c9f0
0x7ffeee79c9f4
0x7ffeee79c9f8

成员对齐与整体对齐

所谓成员对齐,就如上面说的例子

1
2
3
4
5
6
|c1|i|i|i|
|i|c2| | |
变成
|c1| | | |
|i|i|i|i|
|c2| | | |

为了更高的读取效率,可以往后c1添加3个字节,那么为什么一定要添加3个字节?

这就和对齐系数有关了,#pragma pack(n)可以指定对齐系数

不同平台上编译器的 pragma pack 默认值不同。而我们可以通过预编译命令#pragma pack(n), n= 1,2,4,8,16来改变对齐系数

整体对齐与数据对齐相似但不是完全相同

如果数据对齐完成时struct的大小不是struct内成员自身长度最大值(sizeof) 与 #pragma pack(n)中的n的最小值的整数倍

注意这里是成员中长度最大的那个与n比较,而不是特定的一个成员。就要在struct的最后添加空字节直到对齐

例如一个struct在数据对齐之后,整个的大小是6byte,但是对齐系数是4,那么就要往后添加2byte