一.前言
基于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++结构体会以最大成员占用的空间倍数进行对齐分配空间
其中
Student1或Student2最大的成员占用空间为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()判断是否有重写alloc或allocWithZone:,有就走消息转发流程,无就直接申请空间分配。
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时传入的zone为nil,所以最终通过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字节的倍数对齐分配。
434

被折叠的 条评论
为什么被折叠?



