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
NANO_REGIME_QUANTA_SIZE
的值为16,是通过将1左移4位得到的。0000 0001//1
0001 0000//1 << 4 = 16
- step2
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详解
推荐阅读
- 做一件事情的基本原理是什么()
- 2020-04-07vue中Axios的封装和API接口的管理
- iOS中的Block
- 【读书笔记】贝叶斯原理
- SG平滑轨迹算法的原理和实现
- 记录iOS生成分享图片的一些问题,根据UIView生成固定尺寸的分享图片
- “写作宝典”《金字塔原理》之读书笔记
- 2019-08-29|2019-08-29 iOS13适配那点事
- Hacking|Hacking with iOS: SwiftUI Edition - SnowSeeker 项目(一)
- iOS面试题--基础