【OC底层】(3)对象创建之内存分配

一.前言

基于objc4-781
基于64bit运行环境

二.抛砖引玉:思考问题

#import <objc/runtime.h>
#import <malloc/malloc.h>
NSObject *obj = [[NSObject alloc] init];
//	成员变量占空间大小(实际使用空间)
NSLog(@"%zu", class_getInstanceSize(NSObject.class));
//	obj所指向的分配的空间大小(实际分配空间)
NSLog(@"%zu", malloc_size((__bridge const void *)(obj)));

执行输出的结果:

8 16

此时打印obj得到地址:0x100635000
再通过memory read 0x100635000打印出该地址对应的值:

0x100635000: 19 91 ef 8f ff ff 1d 00 00 00 00 00 00 00 00 00 
0x100635010: 2d 5b 4e 53 53 63 72 75 62 62 65 72 54 65 78 74 

通过上面的打印验证NSObject的成员变量空间大小为8Byte,实际分配空间16byte。

三.成员变量的空间是如何分配

观察以下情况的内存分配:

@interface Student1 : NSObject {
    int _no;
    int _age;
    NSObject *_book;
}
@end
@interface Student2 : NSObject {
    int _no;
    NSObject *_book;
    int _age;
}
@end

Student1通过class_getInstanceSize得到成员变量空间大小为24Byte
Student2通过class_getInstanceSize得到成员变量空间大小为32Byte

现象:一样的成员变量但顺序不一样时成员变量空间却不一样
原理:实际上OC的类最终会转为C++的结构体,所以分配成员变量的空间就与C++的结构体相关。
要点:C++结构体会以最大成员占用的空间倍数进行对齐分配空间

其中Student1Student2最大的成员占用空间为8Byte
当连续多个成员变量空间总和不超过最大成员占用的空间时,就会分配在一起,一起分配仍小于最大成员占用的空间时,再以最大成员占用的空间进行对齐,如_no_age连续时,两者空间总和为8Byte,刚好和Student当中最大成员占用的空间一样大,故合用一块8Byte大小的空间;
当连续成员变量空间总和超过最大成员占用的空间时,就会分开分配,小于最大成员占用的空间的再以最大成员占用的空间进行对齐,如_no_book连续时,两者空间总和(4+8Byte)超过最大成员占用的空间8Byte,所以_no单独分配,因对齐需要_no实际分配了8Byte;
所以不同顺序的成员变量空间分配布局是可能不一样的,更多深入的可以去了解C++的结构体内存分配。

四.OC类对象的空间是如何分配

(1) 基于objc4-781版本代码,开源地址: https://opensource.apple.com/tarballs/objc4/
(2) 先介绍两个宏定义:

//  `__builtin_expect`是允许程序员将最有可能执行的分支告诉编译器 
//  fastpath(x)中x为真可能性更大 
//  slowpath(x)中x为假可能性更大 
#define fastpath(x) (__builtin_expect(bool(x), 1))
#define slowpath(x) (__builtin_expect(bool(x), 0))

(2) 通过对初始化流程得到对象的实例会调用到的方法或函数

+ (id)alloc {
    return _objc_rootAlloc(self);
}

id _objc_rootAlloc(Class cls) {
    return callAlloc(cls, false/*checkNil*/, true/*allocWithZone*/);
}

id objc_alloc(Class cls) {
    return callAlloc(cls, true/*checkNil*/, false/*allocWithZone*/);
}

static ALWAYS_INLINE id 
callAlloc(Class cls, bool checkNil, bool allocWithZone=false) {
#if __OBJC2__
    if (slowpath(checkNil && !cls)) return nil;
    if (fastpath(!cls->ISA()->hasCustomAWZ())) {
        return _objc_rootAllocWithZone(cls, nil);
    }
#endif
    // No shortcuts available.
    if (allocWithZone) {
        return ((id(*)(id, SEL, struct _NSZone *))objc_msgSend)(cls, @selector(allocWithZone:), nil);
    }
    return ((id(*)(id, SEL))objc_msgSend)(cls, @selector(alloc));
}

callAlloc中首先通过cls->ISA()->hasCustomAWZ()判断是否有重写allocallocWithZone:,有就走消息转发流程,无就直接申请空间分配。

NEVER_INLINE id 
_objc_rootAllocWithZone(Class cls, malloc_zone_t *zone __unused) {
    // allocWithZone under __OBJC2__ ignores the zone parameter
    return _class_createInstanceFromZone(cls, 0, nil,
                                         OBJECT_CONSTRUCT_CALL_BADALLOC);
}

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();
    //	判断是否支持优化的isa
    bool fast = cls->canAllocNonpointer();
    size_t size;
    //  1.获取实例所需空间大小
    size = cls->instanceSize(extraBytes);
    if (outAllocatedSize) *outAllocatedSize = size;
    id obj;
    if (zone) {
        obj = (id)malloc_zone_calloc((malloc_zone_t *)zone, 1, size);
    } else {
        //  2.分配空间
        obj = (id)calloc(1, size);
    }
    if (slowpath(!obj)) {
        if (construct_flags & OBJECT_CONSTRUCT_CALL_BADALLOC) {
            return _objc_callBadAllocHandler(cls);
        }
        return nil;
    }
    if (!zone && fast) {
        //  快速实例isa通道
        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);
}

调用_class_createInstanceFromZone时传入的zonenil,所以最终通过calloc进行空间分配,需要分配的空间大小从instanceSize函数获得,源码如下:

size_t instanceSize(size_t extraBytes) const {
	/*	objc2基本可以快速实例 */
    if (fastpath(cache.hasFastInstanceSize(extraBytes))) {
        return cache.fastInstanceSize(extraBytes);
    }
    //	非快速实例分支
    size_t size = alignedInstanceSize() + extraBytes;
    // CF requires all objects be at least 16 bytes.
    if (size < 16) size = 16;
        return size;
}
#ifdef __LP64__
#   define WORD_SHIFT 3UL
#   define WORD_MASK 7UL
#   define WORD_BITS 64
#else
#   define WORD_SHIFT 2UL
#   define WORD_MASK 3UL
#   define WORD_BITS 32
#endif
// Fast Alloc fields:
//   This stores the word-aligned size of instances + "ALLOC_DELTA16",
//   or 0 if the instance size doesn't fit.
//   These bits occupy the same bits than in the instance size, so that
//   the size can be extracted with a simple mask operation.
//   FAST_CACHE_ALLOC_MASK16 allows to extract the instance size rounded
//   rounded up to the next 16 byte boundary, which is a fastpath for
//   _objc_rootAllocWithZone()
#define FAST_CACHE_ALLOC_MASK         0x1ff8
#define FAST_CACHE_ALLOC_MASK16       0x1ff0
#define FAST_CACHE_ALLOC_DELTA16      0x0008
static inline size_t word_align(size_t x) {
    return (x + WORD_MASK) & ~WORD_MASK;
}
static inline size_t align16(size_t x) {
    return (x + size_t(15)) & ~size_t(15);
}
bool hasFastInstanceSize(size_t extra) const {
   	if (__builtin_constant_p(extra) && extra == 0) {
      	return _flags & FAST_CACHE_ALLOC_MASK16;
	}
 	return _flags & FAST_CACHE_ALLOC_MASK;
}
size_t fastInstanceSize(size_t extra) const {
	ASSERT(hasFastInstanceSize(extra));
	if (__builtin_constant_p(extra) && extra == 0) {
      	return _flags & FAST_CACHE_ALLOC_MASK16;
 	} else {
       	size_t size = _flags & FAST_CACHE_ALLOC_MASK;
       	// remove the FAST_CACHE_ALLOC_DELTA16 that was added
     	// by setFastInstanceSize
      	return align16(size + extra - FAST_CACHE_ALLOC_DELTA16);
   	}
}

五.总结内存分配

总结:(64bit环境下)对象的空间分配以16字节的倍数对齐分配。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值