引言
内存对齐
是内存里面一个很重要的词汇,可是大部分开发者对这个词汇的含义都是一知半解。
WJPerson*wj = [WJPerson alloc];
wj.name = @"无极";
wj.age = 30;
NSLog(@"对象类型的内存大小:%lu",sizeof(wj));
NSLog(@"对象实际的内存大小:%lu",class_getInstanceSize([wj class]));
NSLog(@"对象分配的内存大小:%lu",malloc_size((__bridge const void *)(wj)));
NSLog(@"-----------------------------------------");
WJPerson*wj2;
NSLog(@"对象类型的内存大小:%lu",sizeof(wj2));
NSLog(@"对象实际的内存大小:%lu",class_getInstanceSize([wj2 class]));
NSLog(@"对象分配的内存大小:%lu",malloc_size((__bridge const void *)(wj2)));
输出结果:
2021-06-16 13:12:08.712404+0800 内存对齐[3440:72350] 对象类型的内存大小:8
2021-06-16 13:12:08.712537+0800 内存对齐[3440:72350] 对象实际的内存大小:24
2021-06-16 13:12:08.712659+0800 内存对齐[3440:72350] 对象分配的内存大小:32
2021-06-16 13:12:08.712746+0800 内存对齐[3440:72350] -----------------------------------------
2021-06-16 13:12:08.712831+0800 内存对齐 [3440:72350] 对象类型的内存大小:8
2021-06-16 13:12:08.712923+0800 内存对齐[3440:72350] 对象实际的内存大小:0
2021-06-16 13:12:08.713007+0800 内存对齐[3440:72350] 对象分配的内存大小:0
结果分析:
-
sizeof
:对象类型的内存大小,sizeof
是用来计算一个变量
或者一个常量
、一种数据类型
所占的内存字节数
。自定义对象的本质是结构体指针
,所以占8
个字节。 -
class_getInstanceSize
:对象实际(对齐后)的内存大小,内存大小是由类的成员变量
的大小决定的。实际上并不是严格意义上的对象的内存的大小,因为内存进行了8
字节对齐,所以wj
的内存大小是24
而不是20
。而wj2只是声明变量,并没有走alloc
方法开辟内存,所以大小是0
。核心内存大小算法是:define WORD_MASK 7UL ((x + WORD_MASK) & ~WORD_MASK
-
malloc_size
:系统实际分配
的内存大小,以16
字节对齐,不足16
的自动补齐。注意:
系统的16字节对齐是在实际的内存大小(经过8
字节对齐后)的基础上。上面的wj对象实际内存大小24
字节,不是16
的倍数,所以系统实际分配为32
。
问题:
class_getInstanceSize
和malloc_size
底层做了什么?我们如何知道class_getInstanceSize
是8
字节对齐,而malloc_size
是16
字节对齐?
在研究后面重点之前,我们先来看下基本数据类型在arm64
环境下占用的内存大小。
下面解释为什么计算机会有内存对齐
的概念,出于什么目的
要内存对齐。
- 内存是以
字节
为基本单位,cpu
在读取数据时,是以块
为单位读取,并不是以字节
为单位读取。频繁
读取未对齐的数据,会加大cpu
的开销。字节对齐后,会降低cpu
的存取次数,这种以空间
换时间
的做法降低了cpu
的开销。 -
cpu
存取:是以块
为单位,存取未对齐
的数据可能开始在上一个内存块,结束在另一个内存块。这样中间可能要经过复杂的运算在合并在一起,降低了效率,字节对齐后,提高了cpu
的访问效率。
内存对齐规则:
数据成员对齐规则:
结构体(struct)(或联合体(union))
的数据成员,第一个数据成员放在offset为0的地方(即首地址的位置),以后每个数据成员存储的起始位置要从该成员大小或者成员的子成员大小(只要该成员有子成员,比如说是数组,结构体等)的整数倍
开始(比如int
为4
字节),则要从4的整数倍
地址开始存储。
结构体
作为成员变量
:如果一个结构体
里有某些结构体成员
,则结构体成员
要从其内部最大元素大小的整数倍
开始存储(struct a
里有struct b
,b里有char,int,double等元素,那b
应该从8(doudle为8 )
的整数倍
开始存储)
下面我们先来看一个例子:
struct内存.gif
struct WJPerson1{
double a;
char b;
int c;
short d;
}myPerson1;
struct WJPerson2
{
double b;
int c;
char a;
short d;
}myPerson2;
NSLog(@"%lu-%lu",sizeof(myPerson1),sizeof(myPerson2));
输出结果:
2021-06-16 16:13:21.621334+0800 内存对齐[4074:158189] 24-16
从上面我们可以看出,myPerson1
和myPerson2
两个结构体
里面元素是一样的,只是顺序不同
,内存大小却不一样
,为什么?这就是结构体内存对齐。
具体分析如下:[p,q]
p
表示当前开始
的位置,q
表示大小
myPerson1:
- double a:
[0,7]
即(0~7
存放a) - char b:
[8,1]
即(8
存放b) - int c:
[12,15]
即(9,10,11不是int 4得出倍数,废弃,12~15
存放c) - short d:
[16,2]
即(16 ~ 17
存放d)
myPerson2:
- double a:
(0,7)
即(0~7
存放a) - int b:
(8,4)
即(8~11
存放b) - char c:
(12,1)
即(12
存放c) - short d:
(14,2)
即(13不是d( short 2的倍数)位置废弃,14 ~ 15
存放d)
下面这个是嵌套的结构体
struct WJPerson3 {
double a;
int b;
char c;
short d;
int e;
struct WJPerson1 str;
} myPerson3;
struct嵌套内存.gif
myPerson3具体分析如下:
- double a:
[0,7]
即(0~7
存放a) - char b:
[8,1]
即(8
存放b) - int c:
[12,15]
即(9,10,11不是int 4得出倍数,废弃,12~15
存放c) - short d:
[16,2]
即(16 ~ 17
存放d) - int e:
(20,4)
即(18,19不是int 4得出倍数,废弃.20 ~ 23
存放d) - 变量str: str是结构体变量,内存对齐原则
结构体成员要从其内部最大元素大小的整数倍地址开始存储
。WJPerson1 中的最大的变量a( double)占8字节,所以offset从24
开始,WJPerson1的内存大小是18
字节。[24,18],即24 ~ 42存放 str,计算出来的是42
个字节,但是myPerson3中最大
的变量是str
和 a都是8
字节,所以myPerson3的实际内存
大小必须是8
的整数倍,42
不是8
的整数倍,因此补齐应该是48
.
网友评论