前言
calloc
方法,作为alloc流程中的核心步骤之一,主要功能是为对象分配内存空间,并返回指向该内存地址的指针。通过前面的学习,我们知道在instacnceSize
方法里计算出了对象需要申请的内存大小,那系统为对象实际分配的内存大小和需要申请的内存大小是一样吗? 接下来,本文将详细讲解在calloc
方法中系统对内存又进行了怎样的处理。
一、分析calloc方法的内存处理
calloc方法的底层实现逻辑,需要在libmalloc
源码中查看,所以先在苹果开源网站下载libmalloc源码的最新版本。本文是基于libmalloc-283.100.6
源码来分析,这里只讲解calloc里的内存处理逻辑,如果对calloc方法的实现流程感兴趣,大家可以自行打断点进行流程跟踪。
通过在libmalloc源码里进行流程跟踪可知,calloc流程最终是在segregated_size_to_fit
方法里对申请的内存大小进行了再次处理。
#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;
//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 <malloc/malloc.h>
。
//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字节对齐,则不同。
推荐阅读
1. iOS原理 OC对象的实例化
2. iOS原理 alloc核心步骤1:instanceSize详解
3. iOS原理 alloc核心步骤3:initInstanceIsa详解