在使用sizeof()计算结构体和class大小的时候,往往很多时候计算出来并不是我们想要的结果。最近在使用的时候就特别想了解它内在的原理,搞懂它的本质。
看一下下面这个例子:
struct Struct{
char a;
int i;
};
问题是:这个结构体大小是多少?
很多人回答可能是5个字节。分析结构体的组成是:char占1个字,int占4个字节。1+4=5,没错吧?
其实这个答案应该是:不确定。因为它给的条件不能够得出一个确定的答案。
打开VS 2017,64位程序执行一下,得出的结果是8。
既然它不是简单的把成员大小简单累加,想必它肯定是有一套规则,那是什么规则呢?那就是内存对齐
-
(what)什么内存对齐(Data alignment)?
百度百科的定义:编译器为程序中的每个“数据单元”安排在适当的位置上。
我的理解是按照一定的规则把它的位置排好,排列得有规有矩,方便计算机读取。 -
(why)为什么要使用内存对齐
为了性能
性能:为什么内存对齐为提高性能呢?举个例子,例如把4个字节从地址1写入到寄存器中。首先从前4个字节中读取3个节字,再从后面4个字节中读了1个字,最后放在寄存器中合并在一齐。读取一个数据到寄存器中消耗了这么多步骤,对于CPU性能来说是挺大的负担。如果是字节对齐了,从0开始连续读取4个字,寄存器刚好也是4个字节,只需要一次读写即可,大大提高了读取效率。
-
(how)内存对齐是如何工作的
想想我们平时要把物品整理对齐的时候,一般都有参照物或对齐的标准。如果把箱子排好,是一个个紧密对齐排列呢,还是间隔排列呢,这个标准会影响排列后使用的空间。
对于内存对齐,同样也是有一个参考标准。编译器中提供了#pragma pack(n)来设定变量以n字节对齐方式,n即为对齐的标准。
VS 2017 64bit的字节对齐变量为16,在代码中插入:#pragma pack(show),就可以查看当前环境的变量值。
有了这个标准,还需要的是被排列的对象:字段类型。
拿回箱子排列的例子来说,可能每一个箱子的大小不一样,有些是短一点,有些是长一点,可能排列的位置也有不一样。
箱子大小就如成员变量,每一个变量有自己的大小。
现在来看一步步分解,系统是如何把位置给安排得明明白白的。
例如这个
struct Struct{
char a;
int i;
};
- 每一块内存内存是从0开始偏移的,第一个字段直接安排下去。
第一个字段char,偏移量(offset)为0,占用空间1个字节,结束位置为1;
字段类型 | 字段名 | 字段偏移量 | 字段占用大小 | 字段结束位置 |
---|---|---|---|---|
char | a | 0 | 1 | 1 |
- 关键一步来了,如何安排int字节呢?
a.int字节是4 bytes,字节对齐变量为16 bytes。比较两者间选择小的一个,此时4 bytes作为偏移参考量X。
b.从当前偏移量1开始,寻找偏移参考量X最小的倍数位置。4的倍数可以是,4,8,12,16等等,当前最小的为4。然而1,2,3的位置空了,需要填充,从第4个byte开始连续4个byte为int的空间。
c.此时8个字节的排布为:a—iiii;
d.画重点:char型占用空间为1 byte,则其起始位置必须可被1整除。int为4 bytes,其起始位置必须是可以被4整除。
字段类型 | 字段名 | 字段偏移量 | 字段占用大小 | 字段结束位置 |
---|---|---|---|---|
char | a | 0 | 1 | 1 |
padding | 1 | 1 | 2 | |
padding | 1 | 1 | 3 | |
padding | 1 | 1 | 4 | |
int | i | 4 | 4 | 8 |
总的内存大小 | 8 |
- 内存对齐的过程如上,影响同一个结构体的内存布局的关键是:以多少字节来对齐,如上面所说的n。最开始的例子,实际上可以通过设置n为1,可以得到size为5。
回顾一下最开始的问题,缺少一个指定变量n的条件,故不能确定大小是多少。