calloc
底层calloc的源码在libMalloc
,不是之前用的libObjc
1 2 3 4 5 void *calloc (size_t num_items, size_t size ){ return _malloc_zone_calloc(default_zone, num_items, size , MZ_POSIX); }
可以看出calloc
调用了_malloc_zone_calloc
函数
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 MALLOC_NOINLINE static void *_malloc_zone_calloc(malloc_zone_t *zone, size_t num_items, size_t size , malloc_zone_options_t mzo) { MALLOC_TRACE(TRACE_calloc | DBG_FUNC_START, (uintptr_t )zone, num_items, size , 0 ); void *ptr; if (malloc_check_start) { internal_check(); } ptr = zone->calloc (zone, num_items, size ); if (os_unlikely(malloc_logger)) { malloc_logger(MALLOC_LOG_TYPE_ALLOCATE | MALLOC_LOG_TYPE_HAS_ZONE | MALLOC_LOG_TYPE_CLEARED, (uintptr_t )zone, (uintptr_t )(num_items * size ), 0 , (uintptr_t )ptr, 0 ); } MALLOC_TRACE(TRACE_calloc | DBG_FUNC_END, (uintptr_t )zone, num_items, size , (uintptr_t )ptr); if (os_unlikely(ptr == NULL )) { malloc_set_errno_fast(mzo, ENOMEM); } return ptr; }
可以看到,如果继续调用就又回到了calloc
函数,这个时候我们借助LLDB打印内存地址。
1 2 p zone->calloc 会输出:(void *(*)(_malloc_zone_t *, size_t , size_t )) $0 = 0x00000001002e1b87 (.dylib`default_zone_calloc at malloc .c:385 )
可以看出这里调用了malloc.c
文件的385行
1 2 3 4 5 6 7 static void *default_zone_calloc(malloc_zone_t *zone, size_t num_items, size_t size ) { zone = runtime_default_zone(); return zone->calloc (zone, num_items, size ); }
default_zone_calloc
里依然会调用calloc
,我们用相同的方法查看具体执行的代码。
1 2 p zone->calloc (void *(*)(_malloc_zone_t *, size_t , size_t )) $1 = 0x00000001002e72cd (.dylib`nano_calloc at nano_malloc.c:878 )
这次执行的代码在nano_malloc.c的878行。nano_calloc函数的实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 static void *nano_calloc(nanozone_t *nanozone, size_t num_items, size_t size ) { size_t total_bytes; if (calloc_get_size(num_items, size , 0 , &total_bytes)) { return NULL ; } if (total_bytes <= NANO_MAX_SIZE) { void *p = _nano_malloc_check_clear(nanozone, total_bytes, 1 ); if (p) { return p; } else { } } malloc_zone_t *zone = (malloc_zone_t *)(nanozone->helper_zone); return zone->calloc (zone, 1 , total_bytes); }
看代码,我们后面应该继续看_nano_malloc_check_clear
函数,分析_nano_malloc_check_clear
函数,内存大小分配应该是调用segregated_size_to_fit
函数。
1 size_t slot_bytes = segregated_size_to_fit(nanozone, size, &slot_key);
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 static MALLOC_INLINE size_t segregated_size_to_fit(nanozone_t *nanozone, size_t size , size_t *pKey) { size_t k, slot_bytes; if (0 == size ) { size = NANO_REGIME_QUANTA_SIZE; } k = (size + NANO_REGIME_QUANTA_SIZE - 1 ) >> SHIFT_NANO_QUANTUM; slot_bytes = k << SHIFT_NANO_QUANTUM; *pKey = k - 1 ; return slot_bytes; }
这里可以看出进行的是 16 字节对齐,那么也就是说我们传入的 size
是 40,在经过 (40 + 16 - 1) >> 4 << 4 操作后,结果为48,也就是16的整数倍。
小结:
isa
底层isa
是一个联合体,是从内存管理层面来设计的,因为联合体是所有成员共享一个内存,联合体内存的大小取决于内部成员内存大小最大的那个元素,对应isa
指针来说,就不用额外声明很多属性,直接在内部ISA_BITFIELD
保存信息。同时由于联合体属性间是互斥的,所以 cls
和 bits
在 isa
初始化流程时是在两个分支中被赋值的。
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 27 28 29 30 31 union isa_t { isa_t () { } isa_t (uintptr_t value) : bits(value) { } uintptr_t bits; private : Class cls; public :#if defined(ISA_BITFIELD) struct { ISA_BITFIELD; }; bool isDeallocating () { return extra_rc == 0 && has_sidetable_rc == 0 ; } void setDeallocating () { extra_rc = 0 ; has_sidetable_rc = 0 ; } #endif void setClass (Class cls, objc_object *obj) ; Class getClass (bool authenticated) ; Class getDecodedClass (bool authenticated) ; };
下面是isa
的初始化
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 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 inline void objc_object::initIsa(Class cls, bool nonpointer, UNUSED_WITHOUT_INDEXED_ISA_AND_DTOR_BIT bool hasCxxDtor) { ASSERT(!isTaggedPointer()); isa_t newisa (0 ) ; if (!nonpointer) { newisa.setClass(cls, this ); } else { ASSERT(!DisableNonpointerIsa); ASSERT(!cls->instancesRequireRawIsa()); #if SUPPORT_INDEXED_ISA ASSERT(cls->classArrayIndex() > 0 ); newisa.bits = ISA_INDEX_MAGIC_VALUE; newisa.has_cxx_dtor = hasCxxDtor; newisa.indexcls = (uintptr_t )cls->classArrayIndex(); #else newisa.bits = ISA_MAGIC_VALUE; # if ISA_HAS_CXX_DTOR_BIT newisa.has_cxx_dtor = hasCxxDtor; # endif newisa.setClass(cls, this ); #endif newisa.extra_rc = 1 ; } isa = newisa; }
isa
是一个联合体,有一个结构体属性是ISA_BITFIELD
,其大小为 8 个字节,也就是 64 位。
1 2 3 4 5 6 7 8 9 10 # define ISA_BITFIELD \ uintptr_t nonpointer : 1 ; \ uintptr_t has_assoc : 1 ; \ uintptr_t has_cxx_dtor : 1 ; \ uintptr_t shiftcls : 44 ; \ uintptr_t magic : 6 ; \ uintptr_t weakly_referenced : 1 ; \ uintptr_t unused : 1 ; \ uintptr_t has_sidetable_rc : 1 ; \ uintptr_t extra_rc : 8
nonpointer
: 表示是否对 isa
指针开启指针优化 0:纯 isa
指针 1:不止是类对象地址, isa
中包含了类信息、对象的引用计数等
has_assoc: 关联对象标志位,0 没有,1 存在
has_cxx_dtor: 该对象是否有 C++ 或者 Objc 的析构器,如果有析构函数,则需要做析构逻辑, 如果没有,则可以更快的释放对象
shiftcls: 存储类指针的值。开启指针优化的情况下,在 arm64 架构中有 33 位用来存储类指针。
magic: 用于调试器判断当前对象是真的对象还是没有初始化的空间
weakly_referenced: 标志对象是否被指向或者曾经指向一个 ARC 的弱变量,没有弱引用的对象可以更快释放。
has_sidetable_rc: 当对象引用技术大于 10 时,则需要借用该变量存储进位
extra_rc: 当表示该对象的引用计数值,实际上是引用计数值减 1, 例如,如果对象的引用计数为 10,那么 extra_rc 为 9。如果引用计数大于 10, 则需要使用到下面的 has_sidetable_rc。
isa
关联对象和类isa
是对象中的第一个属性,因为这一步是在继承的时候发生的,要早于对象的成员变量,属性列表,方法列表以及所遵循的协议列表。
我们在探索 alloc
底层原理的时候,有一个方法叫做 initIsa
。
这个方法的作用就是初始化 isa
联合体位域。其中有这么一行代码:
1 newisa.shiftcls = (uintptr_t )cls >> 3 ;
通过这行代码,我们知道 shiftcls
这个位域其实存储的是类的信息。这个类就是实例化对象所指向的那个类。
这里的左移右移操作其实很好理解,首先我们先观察 isa
的 ISA_BITFIELD
位域的结构:
1 2 3 4 5 6 7 8 9 10 # define ISA_BITFIELD \ uintptr_t nonpointer : 1 ; \ uintptr_t has_assoc : 1 ; \ uintptr_t has_cxx_dtor : 1 ; \ uintptr_t shiftcls : 44 ; \ uintptr_t magic : 6 ; \ uintptr_t weakly_referenced : 1 ; \ uintptr_t unused : 1 ; \ uintptr_t has_sidetable_rc : 1 ; \ uintptr_t extra_rc : 8
我们可以看到,ISA_BITFIELD
的前 3 位是 nonpointer
,has_assoc
,has_cxx_dtor
,中间 44 位是 shiftcls
,后面 17 位是剩余的内容,同时因为 iOS 是小端模式,那么我们就需要去掉右边的 3 位和左边的 17位,所以就会采用 >>3<<3 然后 <<17>>17 的操作了。
通过这个测试,我们就知道了 isa
实现了对象与类之间的关联。
我们还可以探索 object_getClass
底层,可以发现有这样一行代码:
1 return (Class)(isa.bits & ISA_MASK);
这行代码就是将 isa
中的联合体位域与上一个蒙版,这个蒙版定义是怎么样的呢?
1 # define ISA_MASK 0x00007ffffffffff8ULL
x00007ffffffffff8ULL
这个值我们转成二进制表示:
1 2 0000 0000 0000 0000 0111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1000
结果一目了然,这个蒙版就是帮我们去过滤掉除 shiftcls
之外的内容。
我们直接将对象的 isa
地址与上这个mask之后,就会得到 object.class
一样的内存地址。
isa走位分析 类与元类 我们都知道对象可以创建多个,但是类是否可以创建多个呢? 答案很简单,一个。那么如果来验证呢?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 void lgTestClassNum () { Class class1 = [LGPerson class]; Class class2 = [LGPerson alloc].class; Class class3 = object_getClass([LGPerson alloc]); Class class4 = [LGPerson alloc].class; NSLog(@"\n%p-\n%p-\n%p-\n%p" ,class1,class2,class3,class4); } 0x100002108 -0x100002108 -0x100002108 -0x100002108
所以我们就知道了类在内存中只会存在一份。
1 2 3 4 5 6 7 8 9 10 (lldb) x/4 gx LGTeacher.class 0x100001420 : 0x001d8001000013f9 0x0000000100b38140 0x100001430 : 0x00000001003db270 0x0000000000000000 (lldb) po 0x001d8001000013f9 17082823967917874 (lldb) p 0x001d8001000013f9 (long ) $2 = 8303516107936761 (lldb) po 0x100001420 LGTeacher
我们通过上面的打印,就发现 类的内存结构里面的第一个结构打印出来还是 LGTeacher
,那么是不是就意味着 对象->类->类 这样的死循环呢?这里的第二个类其实是 元类
。是由系统帮我们创建的。这个元类也无法被我们实例化。
对象的本质 在我们认知里面,OC
对象的本质就是一个结构体,这个结论在 libObjc
源码的 objc-private.h
源文件中可以得到证实。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 struct objc_object {private : isa_t isa; public : Class ISA () ; Class getIsa () ; ...省略其他的内容... }
而对于对象所属的类来说,我们也可以在 objc-runtime-new.h
源文件中找到
1 2 3 4 5 6 7 8 9 struct objc_class : objc_object { Class superclass; cache_t cache; class_data_bits_t bits; ...省略其他的内容... }
也就是说 objc_class
内存中第一个位置是 isa
,第二个位置是 superclass
。
不过我们本着求真的态度可以用 clang
来重写我们的 OC
源文件来查看是不是这么回事。
1 clang -rewrite-objc main.m -o main.cpp
这行命令会把我们的 main.m
文件编译成 C++
格式,输出为 main.cpp
。
我们可以看到 LGPerson
对象在底层其实是一个结构体 objc_object
。
而我们的 Class
在底层也是一个结构体 objc_class
。