0%

OC对象内存布局

OC对象的内存布局

如下图

注:子类可以拥有和父类同名的实例变量,它们是相互独立的。

isa占用8个字节,是所有对象都拥有的。OC对象最少占用16字节,一些对象可能实际只使用了8个字节,但系统在分配内存时会补足到16字节。isa的offset是0,然后按照内存对齐规则,offset一直增加下去。

由于内存对齐的原因,实例变量在内存中的顺序可能会和我们的书写顺序不一致。个人猜测:编译器会尽量和书写顺序一致,再按照内存对齐规则调整。(所以最好就别猜实例变量的顺序了)

可以使用下面的函数打印offset:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
static NSUInteger FBGetMinimumIvarIndex(__unsafe_unretained Class aCls) {
NSUInteger minimumIndex = 1;
unsigned int count;
Ivar *ivars = class_copyIvarList(aCls, &count);

if (count > 0) {
Ivar ivar = ivars[0];
ptrdiff_t offset = ivar_getOffset(ivar);
minimumIndex = offset / (sizeof(void *));
}

for (int i = 0; i < count; i++) {
Ivar ivar = ivars[i];
ptrdiff_t offset = ivar_getOffset(ivar);
NSUInteger index = offset / (sizeof(void *));
NSLog(@"ivar:%s,offset:%td,index:%lu", ivar_getName(ivar), offset, index);
}

free(ivars);

return minimumIndex;
}

类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@interface FDEDog : NSObject

@property (nonatomic, strong) id property_1_s;
@property (nonatomic, weak) id property_2_w;
@property (nonatomic, unsafe_unretained) id property_3_un;
@property (nonatomic, weak) id property_4_w;
@property (nonatomic, strong) id property_5_s;
@property (nonatomic, strong) id property_6_s;
@property (nonatomic, unsafe_unretained) id property_7_un;
@property (nonatomic, strong) id property_8_s;
@property (nonatomic, strong) id property_9_s;
@property (nonatomic, weak) id property_10_w;
@property (nonatomic, weak) id property_11_w;
@property (nonatomic, strong) id property_12_s;
@property (nonatomic, assign) int property_13_int;
@property (nonatomic, assign) short property_14_short;
@property (nonatomic, weak) id property_15_w;
@property (nonatomic, assign) char property_16_char;
@property (nonatomic, strong) id property_17_s;

@end

打印:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2023-07-17 15:18:23.758865+0800 MLeaksFinderDemo[26862:7944628] ivar:_property_16_char,offset:8,index:1
2023-07-17 15:18:23.758907+0800 MLeaksFinderDemo[26862:7944628] ivar:_property_14_short,offset:10,index:1
2023-07-17 15:18:23.758948+0800 MLeaksFinderDemo[26862:7944628] ivar:_property_13_int,offset:12,index:1
2023-07-17 15:18:23.758978+0800 MLeaksFinderDemo[26862:7944628] ivar:_property_1_s,offset:16,index:2
2023-07-17 15:18:23.759014+0800 MLeaksFinderDemo[26862:7944628] ivar:_property_2_w,offset:24,index:3
2023-07-17 15:18:23.759049+0800 MLeaksFinderDemo[26862:7944628] ivar:_property_3_un,offset:32,index:4
2023-07-17 15:18:23.759077+0800 MLeaksFinderDemo[26862:7944628] ivar:_property_4_w,offset:40,index:5
2023-07-17 15:18:23.759105+0800 MLeaksFinderDemo[26862:7944628] ivar:_property_5_s,offset:48,index:6
2023-07-17 15:18:23.759134+0800 MLeaksFinderDemo[26862:7944628] ivar:_property_6_s,offset:56,index:7
2023-07-17 15:18:23.759160+0800 MLeaksFinderDemo[26862:7944628] ivar:_property_7_un,offset:64,index:8
2023-07-17 15:18:23.759191+0800 MLeaksFinderDemo[26862:7944628] ivar:_property_8_s,offset:72,index:9
2023-07-17 15:18:23.759256+0800 MLeaksFinderDemo[26862:7944628] ivar:_property_9_s,offset:80,index:10
2023-07-17 15:18:23.759309+0800 MLeaksFinderDemo[26862:7944628] ivar:_property_10_w,offset:88,index:11
2023-07-17 15:18:23.759392+0800 MLeaksFinderDemo[26862:7944628] ivar:_property_11_w,offset:96,index:12
2023-07-17 15:18:23.759458+0800 MLeaksFinderDemo[26862:7944628] ivar:_property_12_s,offset:104,index:13
2023-07-17 15:18:23.759509+0800 MLeaksFinderDemo[26862:7944628] ivar:_property_15_w,offset:112,index:14
2023-07-17 15:18:23.759583+0800 MLeaksFinderDemo[26862:7944628] ivar:_property_17_s,offset:120,index:15

另:这里的index是8字节的索引。从0一直到15,总共16个,16*8=128,也是FDEDog实例的大小。

isa结构体字段验证

isa定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
union isa_t {
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }

Class cls; //一个指针指向objc_class结构体。这样就可以兼容旧版本。
uintptr_t bits;
//这里展开的是arm64的
struct {
uintptr_t nonpointer : 1; //LSB。0 is raw isa, 1 is non-pointer isa.
uintptr_t has_assoc : 1; //对象是否有关联对象
uintptr_t has_cxx_dtor : 1; //对象是否有C++或ARC析构函数
uintptr_t shiftcls : 33; /*MACH_VM_MAX_ADDRESS 0x1000000000*/ //Class地址。
uintptr_t magic : 6; //一个魔法数。用于区分是否初始化
uintptr_t weakly_referenced : 1; //是否有弱引用指针。
uintptr_t deallocating : 1; //是否正在被销毁
uintptr_t has_sidetable_rc : 1; //是否有sidetable引用计数。对象的引用计数太大而不能在内部保存时会将引用计数保存到sidetable
uintptr_t extra_rc : 19 //MSB。对象的引用计数超过1时会存在这里。因此extra_rc加1后才是对象的引用计数。由于只有19位所以只有对象的引用计数不太大的时候才保存在这里
};
};

测试代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
@interface Car : NSObject

@property (nonatomic, assign) int mileage;

@end

@interface Truck : Car

@property (nonatomic, assign) int loadCap;

@end

- (void)test_obj_memory_layout {
Truck *truck = [[Truck alloc] init];
truck.loadCap = 10;
truck.mileage = 24;

NSLog(@"truck:%@, cls:%@,%p", truck, truck.class, truck.class);
NSLog(@"class_getInstanceSize([NSObject class]) = %zd", class_getInstanceSize([NSObject class]));
//获取对象实际使用的内存大小<=系统实际分配的内存大小
NSLog(@"class_getInstanceSize([Truck class]) = %zd", class_getInstanceSize([Truck class]));
//获取系统实际分配的内存大小。
NSLog(@"malloc_size = %zd", malloc_size((__bridge const void *)(truck)));
NSLog(@"sizeof(truck) = %zd, sizeof(Truck *):%lu", sizeof(truck), sizeof(Truck *));
//sizeof(truck)和sizeof(Truck *)是等价的,所以都是8.
}

图1:

分析:前八个字节是isa的值,第3个4字节是mileage字段的值,第4个4字节是loadCap字段的值。由于是小端存储,为了方便转化为大端(这里仅转换isa的值)

1
2
3
4
5
6
7
01 00 00 01 04 2B 51 09
=>二进制
0000 0001 0000 0000 000,0 00,00 0000, 0001 04 2B 51 0000 1,001

0x1042b5108 -- 4*8+1 = 33 //这就是类对象的地址,和打印一致。
1:non-pointer isa.最后一位为1说明是non-pointer isa。
当前对象的引用计数存储在isa里,引用计数值为1.

引用计数占用19位,实际使用时又分为两段。

进一步测试,增加两个强引用指针ptr1,ptr2指向对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
- (void)test_obj_memory_layout {
Truck *truck = [[Truck alloc] init];
truck.loadCap = 10;
truck.mileage = 24;

Truck *ptr1 = truck;
Truck *ptr2 = truck;

NSLog(@"truck:%@, cls:%@,%p", truck, truck.class, truck.class);
NSLog(@"class_getInstanceSize([NSObject class]) = %zd", class_getInstanceSize([NSObject class]));
//获取对象实际使用的内存大小<=系统实际分配的内存大小
NSLog(@"class_getInstanceSize([Truck class]) = %zd", class_getInstanceSize([Truck class]));
//获取系统实际分配的内存大小。
NSLog(@"malloc_size = %zd", malloc_size((__bridge const void *)(truck)));
NSLog(@"sizeof(truck) = %zd, sizeof(Truck *):%lu", sizeof(truck), sizeof(Truck *));
}

图2:

分析:前八个字节是isa的值,由于是小端存储,为了方便转化为大端

1
2
3
4
5
6
7
03 00 00 01 00 D9 91 09
=>二进制
0000 0011 0000 0000 000,0 00,00 0000, 0001 00 D9 91 0000 1,001

0x100d99108 -- 4*8+1 = 33
1:non-pointer isa.最后一位为1说明是non-pointer isa。
当前对象的引用计数存储在isa里,引用计数值为3.

参考

「类与对象」如何准确获取对象的内存大小?

觉得文章有帮助可以打赏一下哦!