1、什么是内存对齐以及为什么要内存对齐?
内存对齐:编译器将程序中的每个“数据单元”安排在适当的位置上。
简单理解:按照某种规则将我们定义的结构体成员放在合适的地址偏移位置上存储。
举一个例子:
//32位系统
#include<iostream>
using namespace std;
struct{
int x;
char y;
}s;
int main()
{
cout << sizeof(s) << endl;
return 0;
}
问题:上述代码的输出是多少?
答案:8
为什么要额外的3字节去填充这个结构体?一个原本5字节的结构现在变成8 字节,几乎扩大了 2 倍的存储空间,这样的空间开销是否值得?又是什么样的原因导致这样的设计?
内存对齐的原因
1.内存以字节为单位:
内存是以字节为单位存储,但是处理器并不会按照一个字节为单位去存取内存。处理器存取内存是块为单位,块的大小可以是2,4,8,16字节大小,这样的存取单位称为内存存取粒度。如果在64位的机器上,不论CPU是要读取第0个字节还是要读取第1个字节,在硬件上传输的信号都是一样的。因为它都会把地址0到地址7,这8个字节全部读到CPU,只是当我们是需要读取第0个字节时,丢掉后面7个字节,当我们是需要读取第1个字节,丢掉第1个和后面6个字节。所以对于计算机硬件来说,内存只能通过特定的对齐地址进行访问。
2.内存存取效率:
从内存存取效率方面考虑,内存对齐的情况下可以提升CPU存取内存的效率。比如有一个整型变量(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只需要一次内存读取,并且没有额外的逻辑计算。可见内存对齐之后存取的效率提升了1倍。
总结:
通过填充字段padding使得结构体大小与机器字倍数对齐是一种常见的做法。显然内存对齐是会浪费一些空间的。但是这种空间上得浪费却可以减少存取的时间。这是典型的一种以空间换时间的做法。在内存越来越便宜的今天,这一点点的空间上的浪费就不算什么了。因为访问内存的速度对于处理来说是非常非常的慢, 内存访问速率对于现在 CPU 来说越来越跟不上, 额外的内存访问无疑是浪费 CPU的。
面试真题
1.C++当中一个空的结构体或者类的对象的大小是多少?
答案: 空的类或者结构体的大小是1个字节,因为C++当中每个实例在内存中都有一个独一无二的地址,为了达到这个目的,编译器往往会给一个空类隐含的加一个字节,这样空类在实例化后在内存得到了独一无二的地址。
2.结构体成员的声明顺序会影响结构体的大小么?比如下面两个结构体A,B他们大小是多少?
struct A // sizeof (A) == 12
{
char b;
int a;
char c;
};
struct B // sizeof (B) == 8
{
char b;
char c;
int a;
};
答案:成员声明顺序会影响结构体大小。