目录
NSObject对象内存查看
//创建一个Student对象
Student * student = [[Student alloc] init];
student->_age = 18;
student->_number = 20;
lldb调试:
(lldb) po class_getInstanceSize([student class])
16
(lldb) po malloc_size((__bridge const void*)student);
16
(lldb) po student
<Student: 0x10070c370>
可知申请内存大小为16,分配内存大小为16。起始地址为0x10070c370,所以具体的内存分配如下:
struct Student_IMPL{
Class isa; //8字节,成员地址:0x10070c370
int age; //4字节,成员地址:0x10070c378
int number; //4字节,成员地址:0x10070c37c
}
isa
(lldb) memory read/1xg 0x10070c370
0x10070c370: 0x001d800100001309
isa指针指向的是类对象,后续文章会详细说明。到要得到类对象的地址需要 0x001d800100001309 & MASk 进行运算。
因为,isa 指针要通过 MASK 处理之后才能对应到具体的值。所以先用po指令打印下:
(lldb) po 0x10070c370
<Student: 0x10070c370>
Student就是类对象。<Student: 0x10070c370>表示Student类对象的一个实例对象,对象的内存地址是0x10070c370。
age
(lldb) memory read/1dw 0x10070c378
0x10070c378: 18
number
(lldb) memory read/1dw 0x10070c37c
0x10070c37c: 20
NSObject对象内存优化
1.定义如下结构体:
struct Student_IMPL{
int64_t isa;//用int64_t 代替Class
char _ch1;
long _height;
char _ch2;
int _age;
};
struct Student_IMPL stu;
stu.isa = 100;
stu._ch1 = 'a';
stu._height = 185;
stu._ch2 = 'b';
stu._age = 18;
lldb调试:
查看大小
(lldb) p sizeof(stu)
(unsigned long) $6 = 32
查看结构体地址:
(lldb) p &stu
(Student_IMPL *) $0 = 0x00007ffeefbff5b0
查看各成员的地址:可以看出满足内存对齐
(lldb) p &stu.isa
(int64_t *) $1 = 0x00007ffeefbff5b0
(lldb) p &stu._ch1
(char *) $2 = 0x00007ffeefbff5b8 "a"
(lldb) p &stu._height
(long *) $3 = 0x00007ffeefbff5c0
(lldb) p &stu._ch2
(char *) $4 = 0x00007ffeefbff5c8 "b"
(lldb) p &stu._age
(int *) $5 = 0x00007ffeefbff5cc
内存分配如下表所示
成员变量 | 占用地址 | 大小(字节) |
isa | 0x00007ffeefbff5b0~0x00007ffeefbff5b7 | 8 |
_ch1 | 0x00007ffeefbff5b8 | 1 |
对齐 | 0x00007ffeefbff5b9~0x00007ffeefbff5bf | 7 |
_height | 0x00007ffeefbff5c0~0x00007ffeefbff5c7 | 8 |
_ch2 | 0x00007ffeefbff5c8~0x00007ffeefbff5cb | 4 |
_age | 0x00007ffeefbff5cc~0x00007ffeefbff5cf | 4 |
占用的地址空间为:0x00007ffeefbff5b0~0x00007ffeefbff5cf,共32个字节。
2.创建一个Student对象与Student_IMPL一一对应,我们来看下它的大小及内存分配情况
//Student声明
@interface Student : NSObject
@property (nonatomic, assign) char ch1; // 1个字节
@property (nonatomic, assign) long height; // 8个字节
@property (nonatomic, assign) char ch2; // 1个字节
@property (nonatomic, assign) int age; // 4个字节
@end
//创建一个Student对象
Student * obj = [[Student alloc] init];
// 对象创建赋值
obj.ch1 = 'a';
obj.height = 185;
obj.ch2 = 'b';
obj.age = 18;
lldb调试:
查看大小
(lldb) po class_getInstanceSize([obj class]);
24
那么与结构体相同的数据组成,为什么占用的空间大小一个是32,而一个却是24呢?,下面我们来看下Student实例的内存是如何分配的。
查看地址
(lldb) po obj
<Student: 0x28103a160>
查看实际分配的内存大小:
(lldb) po malloc_size((__bridge const void *)obj);
32
那么obj对象占用的内存空间为:0x28103a160 ~ 0x28103a17f。
下面来看看这段内存内实际存放的数据是什么
(lldb) p &obj->isa
(Class *) $10 = 0x000000028103a160
(lldb) p &obj->_ch1
(char *) $11 = 0x000000028103a168 "ab"
(lldb) p &obj->_height
(long *) $12 = 0x000000028103a170
(lldb) p &obj->_ch2
(char *) $13 = 0x000000028103a169 "b"
(lldb) p &obj->_age
(int *) $14 = 0x000000028103a16c
(lldb) memory read/4xg 0x28103a160
0x28103a160: 0x000001a102a4dd39 0x0000001200006261
0x28103a170: 0x00000000000000b9 0x0000000000000000
内存分配如下图所示
成员变量 | 地址 | 大小(字节) |
isa | 0x000000028103a160 ~ 0x000000028103a167 | 8 |
_ch1 | 0x000000028103a168 | 1 |
_ch2 | 0x000000028103a169 | 1 |
对齐 | 0x000000028103a16a ~ 0x000000028103a16b | 2 |
_age | 0x000000028103a16c ~ 0x000000028103a16f | 4 |
_height | 0x000000028103a170 ~ 0x000000028103a177 | 8 |
对齐 | 0x000000028103a178 ~ 0x000000028103a17f | 8 |
实际需要的内存为:0x28103a160 ~ 0x28103a177,共24个字节 实际分配的内存为:0x28103a160 ~ 0x28103a17f,共32个字节
可以看出成员变量并没有按照书写顺序排放,而是进行了优化。
实际需要的内存空间为:8+1+1+2+4+8=24.
实际分配时因为需要是16的倍数,所以最后补齐8字节,实际分配的空间为:8+1+1+2+4+8+8=32
从这里可以得出,在类的成员变量的内存大小分配的时候;系统会进行一次优化,将能进行对齐优化的成员变量放在一个段里面,节约内存空间,以时间换空间,加快 CPU 读取字节时候的偏移计算复杂度;而不是根据成员声明定义的顺序依次进行排布,这里就调整了顺序,然后直接最大化优化内存字节对齐。
行者常至,为者常成!