上篇中对对象的alloc方式OC底层探索03-常用的alloc,init,new到底做了什么?进行了简单探索。在
alloc
时使用了一个8/16字节对齐算法来计算内存大小,想没想过为什么要这样做呢?
举例对象内存大小
HRTest * test = [HRTest alloc];
test.name = @"Henry"; //8字节
test.hobby = @"woman"; //8字节
test.height = 180.0; //8字节
test.a = 'a'; //1字节
test.ab = 'A'; //1字节
NSLog(@"\n---test类型内存大小%lu\n---HRTest实际占用内存大小%lu\n----HRTest实际内存分配大小%lu",
sizeof(test),
class_getInstanceSize([test class]),
malloc_size((__bridge const void*)(test)));
-
直接计算:size = 8 * 3 + 1 * 2 = 26(猜想)
对象的属性大小计算是需要通过内存对齐来计算的,并不是简单的加法
-
log输出情况
出入还是非常大的,问题出在以下几点?
1. isa指针没有计算在内
/// 出自objc源码
typedef struct objc_class *Class;
struct objc_object {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
};
sizeof(x)是获取当前类型的内存大小,,上文中
sizeof(test)
的test是一个结构体的指针
,也就得到一个指针内存占用8字节
所有的类在OC中最终都会编译为objc_object
(在这个问题中可以看做父类),其中包含一个isa指针,所以需要再加上8字节
。
size = 8 + 8 * 3 + 1 * 2 = 34(猜想)
相比较打印结果中的实际内存占用
还是有一些差距。
2. class_getInstanceSize
接下来就通过源码来看看class_getInstanceSize
这个函数到底是如何计算一个类的内存大小的。
size_t class_getInstanceSize(Class cls)
{
if (!cls) return 0;
return cls->alignedInstanceSize();
}
uint32_t alignedInstanceSize() const {
return word_align(unalignedInstanceSize());
}
// 8字节对齐
# define WORD_MASK 7UL
static inline uint32_t word_align(uint32_t x) {
return (x + WORD_MASK) & ~WORD_MASK;
}
根据源码发现在获取实际大小后,会进行8字节对齐,在下方有详细的计算过程。
size = class_getInstanceSize(34) => 40
- 这就是一个对象实际内存占用的计算过程:
iSA(指针)+ 属性大小 + 8字节对齐
3. HRTest实际内存分配大小却是48
居然这样就不得不去看看malloc_size
,malloc_size
的源码是在libmalloc库
里。可惜没找到对应的实现,换个角度从内存分配方法calloc(1, size)
看起。
void * calloc(size_t num_items, size_t size)
{
...
retval = malloc_zone_calloc(default_zone, num_items, size);
}
void * malloc_zone_calloc(...)
{
...
//核心代码
ptr = zone->calloc(zone, num_items, size);
}
这里对于zone->calloa()没法直接跟进,需要:
![](https://img.haomeiwen.com/i6333164/9bfbb632da637635.png)
static void * default_zone_calloc(...)
{
zone = runtime_default_zone();
return zone->calloc(zone, num_items, size);
}
//使用相同的方式:
static void * nano_malloc(...)
{
...
void *p = _nano_malloc_check_clear(nanozone, size, 0);
}
//很长但是我们的目的是找到内存大小计算的方法
static void * _nano_malloc_check_clear(...)
{
void *ptr;
size_t slot_key;
size_t slot_bytes = segregated_size_to_fit(nanozone, size, &slot_key);
...
}
//终于找到了目标方法:内存计算规则
#define NANO_REGIME_QUANTA_SIZE (1 << SHIFT_NANO_QUANTUM) // 16
static MALLOC_INLINE size_t
segregated_size_to_fit(...)
{
...
k = (size + NANO_REGIME_QUANTA_SIZE - 1) >> SHIFT_NANO_QUANTUM;
slot_bytes = k << SHIFT_NANO_QUANTUM;
*pKey = k - 1;
return slot_bytes;
}
在内存创建的时候系统是对实际内存占用进行了16字节对齐
40 按照16进制对齐 => 48
小结一下
当然在底层中对象的属性所占内存大小计算不简单的是做加法,而是使用了内存对齐
的方法来进行计算,由于篇幅所限会在OC底层探索05-内存对齐
中对内存对齐
做解释。
对象实际内存经过了 => 8字节对齐
=> 16字节对齐
之后,才在内存中分配了对应大小的内存。那么问题又来了为什么要这样做呢?
16字节对齐算法
本质就是通过位运算,将实际内存大小计算为16的倍数
.8字节对齐
也是类似的。
- 第一种方式:
(x + size_t(15)) & ~size_t(15)
//拿31举例:
size(31) : 0001 1111
15 : 0000 1111
size(31) + 15 : 0010 1110
~15 : 1111 0000
size(31) + 15 & ~15 : 0010 0000 -> 32
- 第二种方式:
(x + size_t(15)) >>4 <<4
//拿31举例:
size(31) : 0001 1111
15 : 0000 1111
size(31) + 15 : 0010 1110
>> 4: 0000 0010
<< 4: 0010 0000 -> 32
字节对齐的优势:
-
CPU把内存当成是一块一块的,块的大小可以是2,4,8,16字节大小,因此CPU在读取内存时是一块一块进行读取的。每次内存存取都会产生一个固定的开销,减少内存存取次数将提升程序的性能。
-
16字节对齐后,可以加快CPU读取速度,同时使访问更安全,不会产生访问混乱的情况
早期的iOS系统中对象内存大小计算是通过
8字节对齐
,在分配内存时又进行了16字节对齐
;而现在iOS系统中对象的内存大小计算是直接进行16字节对齐
,通过这种方式来进一步优化对象的创建流程。
网友评论