iOS原理|iOS原理 alloc核心步骤2(calloc详解)

iOS原理 文章汇总
前言 calloc方法,作为alloc流程中的核心步骤之一,主要功能是为对象分配内存空间,并返回指向该内存地址的指针。通过前面的学习,我们知道在instacnceSize方法里计算出了对象需要申请的内存大小,那系统为对象实际分配的内存大小和需要申请的内存大小是一样吗? 接下来,本文将详细讲解在calloc方法中系统对内存又进行了怎样的处理。
一、分析calloc方法的内存处理 calloc方法的底层实现逻辑,需要在libmalloc源码中查看,所以先在苹果开源网站下载libmalloc源码的最新版本。本文是基于libmalloc-283.100.6源码来分析,这里只讲解calloc里的内存处理逻辑,如果对calloc方法的实现流程感兴趣,大家可以自行打断点进行流程跟踪。
通过在libmalloc源码里进行流程跟踪可知,calloc流程最终是在segregated_size_to_fit方法里对申请的内存大小进行了再次处理。
#define SHIFT_NANO_QUANTUM4 #define NANO_REGIME_QUANTA_SIZE (1 << SHIFT_NANO_QUANTUM)// 16static MALLOC_INLINE size_t segregated_size_to_fit(nanozone_t *nanozone, size_t size, size_t *pKey) { size_t k, slot_bytes; //step1 if (0 == size) { size = NANO_REGIME_QUANTA_SIZE; // Historical behavior } //step2 k = (size + NANO_REGIME_QUANTA_SIZE - 1) >> SHIFT_NANO_QUANTUM; // round up and shift for number of quanta slot_bytes = k << SHIFT_NANO_QUANTUM; // multiply by power of two quanta size *pKey = k - 1; // Zero-based!return slot_bytes; }

从代码中可知,最后实际分配的内存大小为slot_bytes,总共进行了2步处理:
  • step1
这里是对之前申请的内存空间大小作判断,若size未0,则将size赋值为16。NANO_REGIME_QUANTA_SIZE的值为16,是通过将1左移4位得到的。
0000 0001//1 0001 0000//1 << 4 = 16

  • step2
这里是将size先加上15,再右移4位,得到k值,然后将k左移4位,得到slot_bytes。假设size的值为24,来看下最后计算的slot_bytes值为多少。
0001 1000//24 0000 1111//15 0010 0111//24 + 15 = 39 0000 0010//39 >> 4 = 2(k) 0010 0000//2 << 4 = 32(slot_bytes)

通过计算可知,当size为24时,最后得到的slot_bytes为32,这即是系统为对象实际分配的内存空间大小。从计算逻辑可知,segregated_size_to_fit方法其实是对申请的内存大小进行『16字节对齐』,最终以16字节的倍数来分配空间,最少分配16字节。
二、验证calloc方法的内存处理 上面讲解了calloc方法会对申请的内存大小作16字节对齐处理,现在来创建一个Person对象进行验证。可以通过malloc_size方法来获取系统为对象实际开辟的内存大小,调用时需要先#import
//Person类里自定义了多种类型的属性 @interface Person : NSObject@property (nonatomic, strong) NSString *name; //8 @property (nonatomic, strong) NSString *sex; //8 @property (nonatomic, assign) int age; //4 @property (nonatomic, assign) long height; //8 @property (nonatomic) char c1; //1 @property (nonatomic) char c2; //1@end//创建Person对象,打印内存大小 Person *person = [[Person alloc] init]; NSLog(@"实际分配的内存空间大小:%lu", malloc_size((__bridge const void *)(person)));

如代码所示,person对象所有属性占用的内存空间大小为38字节,在创建完对象后通过malloc_size方法获取实际分配的内存空间大小并打印。为了验证calloc方法的内存处理逻辑,还会先在class_createInstanceFromZone方法中打印申请的内存空间大小。
static ALWAYS_INLINE id _class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone, int construct_flags = OBJECT_CONSTRUCT_NONE, bool cxxConstruct = true, size_t *outAllocatedSize = nil) { ASSERT(cls->isRealized()); // Read class's info bits all at once for performance bool hasCxxCtor = cxxConstruct && cls->hasCxxCtor(); bool hasCxxDtor = cls->hasCxxDtor(); bool fast = cls->canAllocNonpointer(); //1:计算申请的内存空间大小 size_t size; size = cls->instanceSize(extraBytes); if (outAllocatedSize) *outAllocatedSize = size; //在这里打印size printf("申请的内存空间大小:%lu\n", size); //2.为对象分配内存,并返回内存地址 id obj; if (zone) { obj = (id)malloc_zone_calloc((malloc_zone_t *)zone, 1, size); } else { //zone一般为nil,所以会在这里开辟内存 obj = (id)calloc(1, size); } if (slowpath(!obj)) { if (construct_flags & OBJECT_CONSTRUCT_CALL_BADALLOC) { return _objc_callBadAllocHandler(cls); } return nil; }//3将类和开辟的内存空间关联起来。 if (!zone && fast) { obj->initInstanceIsa(cls, hasCxxDtor); } else { // Use raw pointer isa on the assumption that they might be // doing something weird with the zone or RR. obj->initIsa(cls); }if (fastpath(!hasCxxCtor)) { return obj; }construct_flags |= OBJECT_CONSTRUCT_FREE_ONFAILURE; return object_cxxConstructFromClass(obj, cls, construct_flags); }

通过前面的学习可以知道,在instanceSize方法里完成了需要申请的内存大小的计算。 为了对比更加直观,这里分别打印通过16字节对齐和8字节对齐的计算结果。
  • 16字节对齐
//objc-781源码中的instanceSize方法 size_t instanceSize(size_t extraBytes) const {//方式一:编译器快速计算内存大小(16字节对齐) if (fastpath(cache.hasFastInstanceSize(extraBytes))) { return cache.fastInstanceSize(extraBytes); }//方式二:计算类中所有属性和方法的内存占用 + 额外的字节数0(8字节对齐) size_t size = alignedInstanceSize() + extraBytes; //CF requires all objects be at least 16 bytes. //最少申请16字节的内存大小 if (size < 16) size = 16; return size; }

在最新的objc-781源码中,是通过方式一进行16字节对齐的,所以打印结果为:
申请的内存空间大小:48 实际分配的内存空间大小:48

  • 8字节对齐
//objc-750源码中的instanceSize方法 size_t instanceSize(size_t extraBytes) {size_t size = alignedInstanceSize() + extraBytes; // CF requires all objects be at least 16 bytes. if (size < 16) size = 16; return size; }

在老版本的objc-750源码中采取的是8字节对齐方式,所以打印结果为:
申请的内存空间大小:40 实际分配的内存空间大小:48

通过对比上述两种对齐方式的打印结果可知,calloc方法会对申请的内存大小再进行16字节对齐处理,返回的结果即为系统实际为对象分配的内存空间大小。
三、总结 alloc创建对象时,系统会先经过instanceSize方法计算需要申请多大的内存空间,再在calloc方法里对申请的内存大小进行16字节对齐处理,然后按处理后的结果来给对象分配内存空间,并返回内存地址。系统最终实际为对象分配的内存空间大小为16字节的整数倍,并且最少16字节,如果instanceSize方法里是按16字节对齐的,那实际分配的内存大小和申请的内存大小相同,如果是按8字节对齐,则不同。
推荐阅读 【iOS原理|iOS原理 alloc核心步骤2(calloc详解)】1. iOS原理 OC对象的实例化
2. iOS原理 alloc核心步骤1:instanceSize详解
3. iOS原理 alloc核心步骤3:initInstanceIsa详解

    推荐阅读