前言
在探究内存对齐之前,我们先了解下计算内存大小的三种方式,因为接下来在探讨内存对齐时候,我们需要用到其中的方法,首先定义一个LWPweson类(没有自定义的属性和变量)
LWPerson * p = [LWPerson alloc];
LWPerson * q;
NSLog(@"对象类型占用内存大小--%lu",sizeof(p));
NSLog(@"对象类型占用内存大小--%lu",sizeof(q));
NSLog(@"对象实际内存大小-----%lu",class_getInstanceSize([p class]));
NSLog(@"对象实际内存大小-----%lu",class_getInstanceSize([q class]));
NSLog(@"系统为分配的内存大小--%lu",malloc_size((__bridge const void *)(p)));
NSLog(@"系统为分配的内存大小--%lu",malloc_size((__bridge const void *)(q)));
打印结果如下
2020-09-19 22:59:42.507118+0800 KCObjcTest[4003:298596] 对象类型占用内存大小--8
2020-09-19 22:59:42.507678+0800 KCObjcTest[4003:298596] 对象类型占用内存大小--8
2020-09-19 22:59:42.507780+0800 KCObjcTest[4003:298596] 对象实际内存大小-----8
2020-09-19 22:59:42.507844+0800 KCObjcTest[4003:298596] 对象实际内存大小-----0
2020-09-19 22:59:42.507910+0800 KCObjcTest[4003:298596] 系统分配的内存大小--16
2020-09-19 22:59:42.507963+0800 KCObjcTest[4003:298596] 系统分配的内存大小--0
结果
-
sizeof
传进来的是类型,用来计算这个类型占多大内存,这个在编译器编译阶段就会确定,所以sizeof(p)
和sizeof(q)
的结果都是一样的,p
和q
都是指针类型,指针大小就是8个字节
。 -
class_getInstanceSize
对象的实际内存大小,大小由类的属性和变量来决定,实际上并不是严格意义上的对象内存大小,因为底层进行8字节对齐
算法define WORD_MASK 7UL
((x + WORD_MASK) & ~WORD_MASK
,LWPerson
类中没有其他的属性和变量,但是继承了NSObject
,NSObject
中有一个isa
指针,所以内存大小是8字节
-
malloc_size
系统分配的内存大小是按16字节对齐
的方式,即是按16的倍数
分配 ,不足则系统会自动填充字节(具体的calloc详细流程后续会更新)
内存对齐
内存对齐原则
数据成员对齐规则:结构(struct)(或联合(union))的数据成员,第一个数据成员放在
offset
为0
的地方,以后每个数据成员存储的起始位置要从该成员大小或者成员的子成员大小(只要该成员有子成员,比如说是数组,结构体等)的整数倍开始(比如int在 32 位机为4字节,则要从4的整数倍地址开始存储。结构体作为成员:如果一个结构里有某些结构体成员,
则结构体成员要从其内部最大元素大小的整数倍地址开始存储
.(struct a里存有struct b,b里有char,int ,double等元素,那b应该从8的整数倍开始存储.)收尾工作:结构体的总大小,也就是sizeof的结果,.必须是其内部最大成员的整数倍.不足的要补齐。
内存对齐原则描述的有点多有点复杂,下面会用实例进行详细的说明
各类型所占字节
结构体对齐
对象的本质就是结构体(对象在底层编译成结构体),结构体对齐实际上可以看做是内存对齐,只不过对象可以进行内存优化,接下来,我们用实例进行探究结构体对齐
struct LWStruct1{
long a; // 8
int b; // 4
short c; // 2
char d; // 1
}LWStruct1;
struct LWStruct2{
long a; // 8
char d; // 1
int b; // 4
short c; // 2
}LWStruct2;
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSLog(@"-----%lu------%lu",sizeof(LWStruct1),sizeof(LWStruct2));
}
return 0;
}
2020-09-20 15:29:13.535102+0800 KCObjcTest[1632:89696] -----16------24
从打印结果我们发现 LWStruct1
和 LWStruct2
所包含的变量是一样的,只是位置不一样,但是内存大小不一样,为什么? 其实这就是我们一直说的内存对齐
下面我们就根据内存对齐原则
来进行简单的分析和计算
LWStruct1
内存大小的详细过程
- 变量
a
: 占8
个字节,从0
开始,min(0,8)
,即0 ~ 7
存储a
- 变量
b
: 占4
个字节,从8
开始,min(8,4)
,即8 ~ 11
存储b
- 变量
c
: 占2
个字节,从12
开始,min(12,2)
,即12~ 13
存储c
- 变量
d
: 占1
个字节,从14
开始,min(14,1)
,即14
存储d
因此LWStruct1
的内存大小是15字节
,而LWStruct1
中最大的变量是a
占8个字节
,所以LWStruct1
需要实际内存必须是8的倍数
,15字节
不是8的倍数
,所以系统自动填充成16字节
,最终sizeof(LWStruct1)
的大小是16
LWStruct1
解析图如下
LWStruct2
内存大小的详细过程
- 变量
a
: 占8
个字节,从0
开始,min(0,8)
,即0 ~ 7
存储a
- 变量
d
: 占1
个字节,从8
开始,min(8,1)
,即8
存储d
- 变量
b
: 占4
个字节,从9
开始,min(9,4)
,9 % 4 != 0
,继续往后移动直到找到可以整除4
的位置12
,min(12,4)
,即12 ~ 15
存储b
- 变量
c
: 占2
个字节,从16
开始,min(16,2)
,即16 ~ 17
存储c
因此LWStruct2
的内存大小是18字节
,而LWStruct2
中最大的变量是a
占8个字节
,所以LWStruct2
需要实际内存必须是8的倍数
,18字节
不是8的倍数
,所以系统自动填充成24字节
,最终sizeof(LWStruct2)
的大小是24
LWStruct2
解析图如下
结构体中嵌套结构体
继续用实例探究
struct LWStruct2{
long a; // 8
char d; // 1
int b; // 4
short c; // 2
}LWStruct2;
struct LWStruct3{
long a; // 8
int b; // 4
short c; // 2
char d; // 1
struct LWStruct2 lwStr;
}LWStruct3;
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
NSLog(@"-----%lu------%lu",sizeof(LWStruct2),sizeof(LWStruct3));
}
return 0;
}
2020-09-20 17:07:41.507870+0800 KCObjcTest[2467:159943] -----24------40
LWStruct3
内存大小的详细过程
- 变量
a
: 占8
个字节,从0
开始,min(0,8)
,即0 ~ 7
存储a
- 变量
b
: 占4
个字节,从8
开始,min(8,4)
,即8 ~ 11
存储b
- 变量
c
: 占2
个字节,从12
开始,min(12,2)
,即12~ 13
存储b
- 变量
d
: 占1
个字节,从14
开始,min(14,1)
,即14
存储d
- 变量
lwStr
:结构体变量lwStr
,根据内存对齐原则结构体成员要从其内部最大元素大小的整数倍地址开始存储
,LWStruct2
中最大的变量是8
,所以从16
位置开始存储,即LWStruct2
存储16-33
位置,
因此LWStruct3
的内存大小是34字节
,而MyStruct3
中最大变量为 lwStr
, 其最大成员内存字节数为8,所以LWStruct13
内存必须是8的倍数
,34字节
不是8的倍数
,所以系统自动填充成40字节
,最终sizeof(LWStruct3)
的大小是40
LWStruct3
解析图如下
LWStruct3
中的 lwStr
任然按照LWStruct2
排列,不在详细排列
内存优化
下面通过实例来看下内存优化是怎么优化的
int main(int argc, const char * argv[]) {
@autoreleasepool {
LWPerson * p = [LWPerson alloc];
p.nickName = @"hello";
p.age = 18;
p.height = 183;
}
return 0;
}
打印结果如下
下面我们在 LWPerson添加两个属性,继续探究
int main(int argc, const char * argv[]) {
@autoreleasepool {
LWPerson * p = [LWPerson alloc];
p.nickName = @"hello";
p.age = 18;
p.height = 183;
p.a = 'a';
p.b = 'b';
}
return 0;
}
打印结果如下
通过lldb断点打印可以看出 ,age
的读取通过 0x00000012
,a
的读取通过0x61(a的ASCII码是97)
,b
的读取通过0x62(b的ASCII码是98)
我们神奇的发现 int age
,char a
,char b
,共用了一个8字节
内存空间,而且对象的属性或者变量存储顺序和结构体的也不一样,这就是内存优化
总结
其实内存对齐只是制定了一套规则,目的是提高cpu的读取效率和安全的访问,通过字节对齐虽然这样浪费了大量的内存,但是同时又进行内存优化尽可能的降低了内存了浪费