先来一道大厂面试题:
struct Data1
{
char a;
uint32_t b;
short c;
char d;
}test1[3];
sizeof(test1)=?
这道题就是在考查内存对齐。至于答案是什么?别急,看完你就知道了。
什么是内存对齐
内存对齐本质就是数据存储的一个规则,即,数据按照对齐规则在内存中储存。
那么为什么会需要内存对齐呢?对齐的规则又是什么?
为什么需要内存对齐
以上面的面试题为例,假设没有内存对齐,那么结构体中的成员变量的内存就是连续分布的,如下图。
但在访问结构体变量时,是按结构体中成员所占的最大字节数对结构体进行读取的,而且每次只能从储存边界开始读取。在这个例子中,会按照uint32的大小,一次读取4个字节,每次只能从0、4开始读取。(注意:0-7代表相对于首地址的offset,并非实际地址。)
如果我们要获取b的内容,就需要两次读周期才可以将变量b从内存中读取出来,而且,还需要剔除和拼接操作。这就大大增加了读取的时间。
如果我们进行内存对齐呢?如下图所示,(pad代表我们为了进行内存对齐而填充的内存。)我们在读取b时,只用一个读周期就可以了,而且不需要剔除、不需要拼接。这与不对齐相比,是不是效率更高了呢。
看了上面的例子,现在清楚为啥要内存对齐了吧。主要就是为了性能,内存对齐可以提高数据访问效率。而且有的硬件架构,就要求你必须内存对齐,否则编译不通过。
对齐规则
一般平台上的编译器都有自己默认的“对齐系数”,可以通过预编译指令#pragma pack(n),n=1,2,4,8,16来改变对齐系数。(vs\vc中默认是n=8,gcc默认是n=4)
- 基本原则:对于标准数据类型,它的地址只要是它的长度的整数倍就可以了。
- 结构体对齐:
- 结构体第一个成员的偏移量为0(也就是第一个成员变量的地址就是结构体的地址)
- 以后每个成员相对于结构体首地址的offset=i*min(对齐系数,该成员长度),i为大于等于1的整数。就以上面的例子说明,变量a的offset为0,变量b的offset为多少呢?假设设置的对齐系数是8,变量b的长度是4,b的offset就是1*min(8,4)=4。(编译器会插入填充字节,确保满足对齐要求)
- 结构体总体大小为有效对齐值的整数倍。(有效对齐值=min(对齐系数,最长变量的长度)。)上例中,长度最长的变量是b,长度为4,则有效对齐值=4。
答案
看完以上这些,你们知道开头那道题的答案了吗?
sizeof(test1)=36