阿里面试题分析与延展之:为什么要内存对齐
前言
阿里的面试非常的喜欢问体系结构相关的问题。比如我在秋招阿里云polardb团队终面当中被问到的这个问题:
你知道什么是内存对齐以及为什么要内存对齐么?
举一个例子:
下面是一段C代码,在一台32位的机器上。
//32位系统 #include<stdio.h> struct{ int x; char y; }s; int main() { printf("%d\n",sizeof(s); return 0; }
-
问题:上述代码的输出是多少?
如果你的答案是5个字节那么下面的文章或多或少肯定会给你帮助。
正确的答案是8个字节。原因就是内存对齐。
什么是内存对齐
内存对齐就是:编译器将程序中的每个“数据单元”安排在适当的位置上。
为什么要内存对齐
-
内存是以字节为单位:
处理器并不会按照一个字节为单位去存取内存。CPU把内存当成是一块一块的,块的大小可以是2,4,8,16字节大小,我们将上述这些存取单位称为内存存取粒度。对于现代计算机硬件来说,内存只能通过特定的对齐地址(比如按照机器字)进行访问。举个例子来说,比如在64位的机器上,不管我们是要读取第0个字节还是要读取第1个字节,在硬件上传输的信号都是一样的。因为它都会把地址0到地址7,这8个字节全部读到CPU,只是当我们是需要读取第0个字节时,丢掉后面7个字节,当我们是需要读取第1个字节,丢掉第1个和后面6个字节。
-
假如没有使用内存对齐:
比如有一个整型变量(4 字节),现在有一块内存单元: 地址从 0~7。这个整型变量从 地址为 1 的位置开始占据了 1,2,3,4 这 4 个字节。 现在处理器需要读取这个整型变量。假设处理器是 4 字节 4 字节的读取,所以从 0 开始读读取 0,1,2,3发现并没有读完整这个变量,那么需要再读一次,读取 4,5,6,7。然后对两次读取的结果进行处理,提取出 1,2,3,4 地址的内容。需要两次访问内存,同时通过一些逻辑计算才能得到最终的结果。
如果进行内存对齐,将这个整型变量放在从0开始的地址存放,那么CPU只需要一次内存读取,并且没有额外的逻辑计算。
-
内存对齐的好处
性能原因:减少CPU读取内存的次数,提升程序执行的效率
上图是CPU和几种存储之间的存取速度在这30多年的发展对比。内存就是上述的DRAM存储,CPU的速度和内存 的速度之间差距接近1000倍,3个数量级的差距。可见如果能够减少对内存的读取次数可以极大的提升程序的执 行效率。
对齐规则
内存对齐主要遵循下面三个原则:
- 结构体变量的起始地址能够被其最宽的成员大小整除
- 结构体每个成员相对于起始地址的偏移能够被其自身大小整除,如果不能则在前一个成员后面补充字节
- 结构体总体大小能够被最宽的成员的大小整除,如不能则在后面补充字节
谁去完成内存对齐的工作
内存对齐总结
补充知识
其他的对齐规则:
我们知道计算机体系结构当中缓存是很重要的一环,CPU不是直接读取内存而是读取缓存:高速缓冲存储器,其作用是为了更好的利用局部性原理,减少CPU访问主存的次数。因为存取内存相对存取缓存是慢很多的,***也可以看做是一种空间换时间的做法。实际读取内存的是缓存。所以内存对齐有的时候还需要考虑缓存更新的读取策略,一些规则如下:
-
对较大结构体进行*** LINE对齐
***与内存交换的最小单位为*** LINE。一个*** LINE大小以64字节为例。当我们的结构体大小没有与64字节对齐时,一个结构体可能就要占用比原本需要更多的*** LINE。
还有叫做错误共享的问题,大家可以自行google。
-
只读字段和读写字段隔离对齐
只读字段和读写字段隔离对齐的目的就是为了尽量保证那些只读字段和读写字段分别集中在***的不同*** LINE中。使得读写字段的淘汰尽量少的影响只读字段。
延伸面试题
-
C++当中一个空的结构体或者类的对象的大小是多少?
答案: 空的类或者结构体的大小是1个字节,因为C++当中每个实例在内存中都有一个独一无二的地址,为了达到这个目的,编译器往往会给一个空类隐含的加一个字节,这样空类在实例化后在内存得到了独一无二的地址。 -
结构体成员的声明顺序会影响结构体的大小么?比如下面两个结构体A,B他们大小是多少?struct A // sizeof (A) == 12 { char b; int a; char c; }; struct B // sizeof (B) == 8 { char b; char c; int a; };
答案:成员声明顺序会影响结构体大小。