在iOS中所有的类都是继承于NSObject,但是在底层NSObject是由struct结构体组成的,那么结构体的内存是如何分配的呢?类的内存是如何分配的呢?
一. 结构体内存对齐
先让我们看一个小案例:
//1、定义两个结构体
struct AAA{
char a; //1字节
double b; //8字节
int c; //4字节
short d; //2字节
}AAA;
struct BBB{
double b; //8字节
int c; //4字节
short d; //2字节
char a; //1字节
}BBB;
//计算 结构体占用的内存大小
NSLog(@"%lu-%lu",sizeof(AAA),sizeof(BBB));
输出结果是:
疑问:结构体AAA和BBB属性类型、数量都是一样,只有顺序不同,难道内存对齐和顺序有关?
1 内存对齐规则
-
数据成员对⻬规则:结构(struct)(或联合(union))的数据成员,第⼀个数据成员放在offset为0的地⽅,以后每个数据成员存储的起始位置要从该成员⼤⼩或者成员的⼦成员⼤⼩(只要该成员有⼦成员,⽐如说是数组,结构体等)的整数倍开始(⽐如int为4字节,则要从4的整数倍地址开始)存储。
-
嵌套结构体:如果⼀个结构⾥有某些结构体成员,则结构体成员要从其内部最⼤元素⼤⼩的整数倍地址开始存储.(struct a⾥存有struct b,b⾥有char,int ,double等元素,那b应该从8的整数倍开始存储.)。
-
最⼤成员的整数倍: 结构体的总⼤⼩,也就是sizeof的结果,.必须是其内部最⼤成员的整数倍.不⾜的要补⻬。
下表是各种数据类型在ios中的占用内存大小,根据对应类型来计算结构体中内存大小。
1.1 数据成员对⻬规则
当前成员的开始位置 可以整除 当前成员所需要的位数,如果不可以整除就加1,以此类推。
在上述案例中,结构体AAA的内存计算方式:
struct AAA{
char a; //1字节
double b; //8字节
int c; //4字节
short d; //2字节
}AAA;
- 变量a: 从0开始,长度为1, 0 / 1 = 0,可以整除,那么a就存在0号位,且长度为1。
- 变量b:从8开始,长度为8, a结束完是从1开始,但是1 / 8 = 0.125, 不能整除,+1…;(1 + 7)/ 8 = 1,可以整除了,所以那么b就存在8、9、10、11、12、13、14、15号位,长度为8。
- 变量c:从16号位开始,长度是4,16 / 4 = 4,可以整除。那么c就存在16、17、18、19号位,长度为4。
- 变量d:从20号位开始,长度位2,20 / 2 = 10,可以整除;那么d就存在21、22号位,长度位2。
- AAA总的内存是22,但是结构体的大小是最⼤成员的整数倍,也就是8的整数倍,于22最为接近的整数倍是24,所以AAA的内存大小是24。
结构体BBB的内存计算方式:
struct BBB{
double b; //8字节
int c; //4字节
short d; //2字节
char a; //1字节
}BBB;
- 变量b::从0开始,长度为8, 0 / 8 = 0, 可以整除。那么b就存在0、1、2、3、4、5、6、7号位,长度为8。
- 变量c:从8号位开始,长度是4,8 / 4 = 2,可以整除。那么c就存在8、9、10、11号位,长度为4。
- 变量d:从12号位开始,长度位2,12 / 2 = 6,可以整除;那么d就存在12、13号位,长度位2。
- 变量a:从14开始,长度为1, 14 / 1 = 14,可以整除,那么a就存在14号位,且长度为1。
- BBB总的内存是14,但是结构体的大小是最⼤成员的整数倍,也就是8的整数倍,于14最为接近的整数倍是16,所以AAA的内存大小是16。
1.2 嵌套结构体
struct BBB{
double b; //8字节
int c; //4字节
short d; //2字节
char a; //1字节
struct AAA aaa; //24字节
}BBB;
当结构体BBB中嵌套结构体AAA时,作为数据成员的结构体变量aaa,它的开始存储的位数是:aaa中最大成员变量的倍数,也就是aaa中最大的成员变量是b变量8个字节,必须是8的倍数。
- 变量b::从0开始,长度为8, 0 / 8 = 0, 可以整除。那么b就存在0、1、2、3、4、5、6、7号位,长度为8。
- 变量c:从8号位开始,长度是4,8 / 4 = 2,可以整除。那么c就存在8、9、10、11号位,长度为4。
- 变量d:从12号位开始,长度位2,12 / 2 = 6,可以整除;那么d就存在12、13号位,长度位2。
- 变量a:从14开始,长度为1, 14 / 1 = 14,可以整除,那么a就存在14号位,且长度为1。
- 变量aaa:从aaa中最大的成员变量是b变量8个字节,那么开始存储的位数必须是8的整数倍。所以从16号位开始,存储在[16 - 39]中,长度是24。
- BBB总的内存是39,但是结构体的大小是最⼤成员的整数倍,也就是8的整数倍,于39最为接近的整数倍是40,所以BBB的内存大小是40。
二、类内存对齐
思考: 已知在结构体内存对齐中,属性的顺序与内存是息息相关的,如果我们从小到大的顺序是比较浪费内存,但是从大到小就节省内存。那么在iOS类对象中如何进行内存对齐的呢?
内存优化
在iOS中类的内存对齐使用的就是结构体的内存对齐,但是在iOS中,将类中的属性进行重排,来达到优化内存的目的。
声明LGPerson类:
@interface LGPerson : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *nickName;
// @property (nonatomic, copy) NSString *hobby;
@property (nonatomic, assign) int age;
@property (nonatomic, assign) long height;
@property (nonatomic) char c1;
@property (nonatomic) char c2;
@end
@implementation LGPerson
@end
在main.h中声明并打印LGPerson的内存:
int main(int argc, char * argv[]) {
@autoreleasepool {
LGPerson *person = [LGPerson alloc];
person.name = @"Cooci";
person.nickName = @"KC";
person.age = 18;
person.c1 = 'a';
person.c2 = 'b';
NSLog(@"%@",person);
}
return 0;
}
0x0000000103eb0038
解析:
- 0x0000000103eb27d0: 作为类对象的ISA,占8个字节。
- 0x0000001200006261: 是有三个属性在这8个字节中,其中:0x00000012是属性age,内容是18,占4个字节;0x62是属性c2,内容是b(ASCII码是98),占1个字节;0x61是属性c1,内容是a(ASCII码是97),占1个字节;
- 0x0000000103eb0038: 是属性name。
- 0x0000000103eb0058:是属性nickName。
- 0x0000000000000000: 是属性没有进行赋值。
iOS中获取内存大小的方式
- sizeof
sizeof最终得到的结果是该数据类型占用空间的大小。
- class_getInstanceSize
用于获取类的实例对象所占用的内存大小,并返回具体的字节数,其本质就是获取实例对象中成员变量的内存大小。
- malloc_size
获取系统实际分配的内存大小。
8内存字节对齐和16字节对齐
在iOS中类对象是以8字节对齐的,但是分配是以16字节对齐分配的。
未待完续…