iOS底层原理03:malloc源码分析

1,141 阅读3分钟

前言:从alloc的源码分析中,我们知道了alloc的三个核心操作:

  • 计算要开辟的内存大小:size = cls->instanceSize(extraBytes)
  • 申请内存:obj = (id)calloc(1, size);
  • 将当前的类和指针地址绑定在一起:obj->initInstanceIsa(cls, hasCxxDtor);

这篇文章将从源码分析calloc。

objc4中alloc 源码

  • 我们先从alloc源码中找到obj = (id)calloc(1, size),主要涉及的方法alloc --> _objc_rootAlloc --> callAlloc --> _objc_rootAllocWithZone --> _class_createInstanceFromZone
  • 点击进入calloc方法,发现calloc方法是在malloc源码中的。
  • 于是去苹果官方下载最新的libmalloc源码

libmalloc源码分析calloc

  • 1、在可编译的libmalloc源码中,新建一个Target,File->New->Target->Application->Command Line Tool,在main中用calloc创建一个指针。
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
		void *p = calloc(1, 40);
        NSLog(@"Hello, World!");
    }
    return 0;
}
  • 2、点击进入calloc方法,不难看出,关键代码是malloc_zone_calloc方法
void *
calloc(size_t num_items, size_t size)
{
	void *retval;
	retval = malloc_zone_calloc(default_zone, num_items, size);
	if (retval == NULL) {
		errno = ENOMEM;
	}
	return retval;
}
  • default_zone是一个默认的zone,目的就是引导程序进入一个创建真正zone的流程
  • 3、点击进入malloc_zone_calloc的源码实现
void *
malloc_zone_calloc(malloc_zone_t *zone, size_t num_items, size_t size)
{
	MALLOC_TRACE(TRACE_calloc | DBG_FUNC_START, (uintptr_t)zone, num_items, size, 0);

	void *ptr;
	if (malloc_check_start && (malloc_check_counter++ >= malloc_check_start)) {
		internal_check();
	}

	ptr = zone->calloc(zone, num_items, size);
	
	if (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);
	return ptr;
}
  • 通过对源码的分析,可以发现关键的代码是:ptr = zone->calloc(zone, num_items, size);
  • 这里的zone是上一步传入的default_zone,关键代码申请了一个指针ptr,并且将这个指针返回。
  • 4、点击进入calloc的源码实现,发现是一个calloc的声明,源码就无法继续往下走了

那么重点来了,这种情况要怎么往下分析呢?

  • 5、往下分析源码的两种方式:
  • 在ptr = zone->calloc(zone, num_items, size)处打个断点,运行代码,这里有两种方式是代码往下走:
    • 1、按住control + step into,进入calloc的源码实现
    • 2、lldb命令输入p zone->calloc

从打印的结果看出zone->calloc的源码实现是在default_zone_calloc方法中

  • 6、全局搜索default_zone_calloc,进入源码,发现此时代码无法往下走
static void *
default_zone_calloc(malloc_zone_t *zone, size_t num_items, size_t size)
{
	zone = runtime_default_zone(); // 创建zone
	
	return zone->calloc(zone, num_items, size); 
}
  • 7、继续使用上述的两种方式,在return zone->calloc(zone, num_items, size)处打个断点,lldb命令输入p zone->calloc
  • 8、全局搜索nano_calloc,进入源码
    • 关键代码886行,如果size小于NANO_MAX_SIZE,则去请了一个指针p,并且将这个指针返回。
    • 不满足条件,则执行helper_zone流程。
  • 9、进入_nano_malloc_check_clear源码,将if else 折叠,看主流程
    • segregated_next_block 这里是开辟内存的算法,并且返回合适的内存。
    • slot_bytes 是加密算法的盐
    • segregated_size_to_fit 是加密算法盐的源码
  • 10、进入segregated_size_to_fit源码,本质是16字节对齐算法。
#define SHIFT_NANO_QUANTUM		4
#define NANO_REGIME_QUANTA_SIZE	(1 << SHIFT_NANO_QUANTUM)	// 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; // Historical behavior
	}
	//k + 15 >>4  右移四位  << 左移四位
	
	//假设k = 2,算法如下:
	//(2 + 15)
	//0001 0001 >>4   ----> 0000 0001 相当于后四位抹零
	//0000 0001 <<4   ----> 0001 0000 等于16,16字节对齐
	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;
}

以上就是对malloc 源码的分析思路,后续再进行完善。