OC底层探索 - _class_createInstanceFromZone

463 阅读7分钟

本文探索OC底层探索 - alloc & init中,并未解释的 _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();
    size_t size;

    size = cls->instanceSize(extraBytes);
    if (outAllocatedSize) *outAllocatedSize = size;

    id obj;
    if (zone) {
        obj = (id)malloc_zone_calloc((malloc_zone_t *)zone, 1, size);
    } else {
        obj = (id)calloc(1, size);
    }
    if (slowpath(!obj)) {
        if (construct_flags & OBJECT_CONSTRUCT_CALL_BADALLOC) {
            return _objc_callBadAllocHandler(cls);
        }
        return nil;
    }

    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);
}

_class_createInstanceFromZone 方法真正的实现了实例的内存分配,那么具体是如何实现的呢?我们慢慢来看。

实例内存大小的计算

首先来看 instanceSize , 该方法名字表示这是实例的大小

size_t size;
size = cls->instanceSize(extraBytes);

我们进来看一下他的实现

inline size_t instanceSize(size_t extraBytes) const {
    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;
}

关于 if 条件中的部分,根据命名猜测,大概应该是缓存已经计算过的类的实例大小,如果存在直接返回。

那么我们就看一下下面真正计算实例大小的方法好了。

size_t size = alignedInstanceSize() + extraBytes;

其中 extraBytes 是通过方法外部传入的,额外字节数。alignedInstanceSize() 是对齐实例的大小。然后有一个判断,使得 size 的大小,至少是 16 字节。

uint32_t alignedInstanceSize() const {
    return word_align(unalignedInstanceSize());
}


#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

/* 字节对齐算法 */
static inline uint32_t word_align(uint32_t x) {
    return (x + WORD_MASK) & ~WORD_MASK;
}


// 可能是未对齐的,这取决于类的成员变量
// May be unaligned depending on class's ivars. 
uint32_t unalignedInstanceSize() const {
    ASSERT(isRealized());
    return data()->ro()->instanceSize;
}

通过上面代码,可以知道,这是先得到未对齐的实例大小,然后进行8字节对齐得到的对齐后的实例大小。(关于对象字节对齐的内容,请看下面 字节对齐计算 内容)

8字节对齐这是以64位设备而言的,32位是4字节对齐, 通过上面源码中的宏可以看出来

if (fastpath(cache.hasFastInstanceSize(extraBytes))) {
    return cache.fastInstanceSize(extraBytes);
}
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);
    }
}
static inline size_t align16(size_t x) {
    return (x + size_t(15)) & ~size_t(15);
}
void setFastInstanceSize(size_t newSize)
{
    // Set during realization or construction only. No locking needed.
    uint16_t newBits = _flags & ~FAST_CACHE_ALLOC_MASK;
    uint16_t sizeBits;

    // Adding FAST_CACHE_ALLOC_DELTA16 allows for FAST_CACHE_ALLOC_MASK16
    // to yield the proper 16byte aligned allocation size with a single mask
    sizeBits = word_align(newSize) + FAST_CACHE_ALLOC_DELTA16;
    sizeBits &= FAST_CACHE_ALLOC_MASK;
    if (newSize <= sizeBits) {
        newBits |= sizeBits;
    }
    _flags = newBits;
}

通过上面源码看,缓存中的size是16字节对齐的,那这个跟我们8字节计算的到的值不一致啊。

id obj;
    if (zone) {
        obj = (id)malloc_zone_calloc((malloc_zone_t *)zone, 1, size);
    } else {
        obj = (id)calloc(1, size);
    }

找网上别人的解释是,calloc 方法内部是会进行16字节对齐的。

为什么是16位对齐

1、通常内存是一个个字节组成的,cpu在存取数据时,并不是以字节为单位存储,而是以块为单位存取,块的大小为内存存取力度,频繁存取字节未对齐的数据,会极大降低CPU的性能,所以可以通过减少存取次数来降低cup的开销 (空间换时间) 2、16字节对齐是因为一个对象中的第一个属性isa8字节,当然对象中肯定还有其他属性,当无属性时会预留8字节,即16字节对齐,如果不预留相当于这个对象的isa和其它对象的isa紧挨着,容易造成访问混乱
3、16字节对齐后,可以加快cpu的存取速度,同时增加访问安全性

这个暂时没有深入研究,不过通过打印测试可以验证。

#import <objc/runtime.h>
#import <malloc/malloc.h>

NSObject *obj = [[NSObject alloc] init];
NSLog(@"NSObject实例size:%lu",sizeof(obj));
NSLog(@"NSObject实例分配的内存大小:%lu",malloc_size((__bridge const void*)(obj)));
void *a = calloc(1, sizeof(obj));
NSLog(@"size_t = %lu 时 calloc方法 实际分配的内存大小:%lu",sizeof(obj), malloc_size(a));

NSLog(@"calloc(1, 1) = %lu", malloc_size(calloc(1, 1)));
NSLog(@"calloc(1, 5) = %lu", malloc_size(calloc(1, 5)));
NSLog(@"calloc(1, 8) = %lu", malloc_size(calloc(1, 8)));
NSLog(@"calloc(1,13) = %lu", malloc_size(calloc(1, 13)));
NSLog(@"calloc(1,16) = %lu", malloc_size(calloc(1, 16)));
NSLog(@"calloc(1,17) = %lu", malloc_size(calloc(1, 17)));

image.png

那么计算的8字节对齐的和缓存的16字节对齐的值,对实际分配的内存的大小来说都是16。

分配内存与实例进行关联

这个看看名字和判断应该是内存分配失败时的处理

if (slowpath(!obj)) {
    if (construct_flags & OBJECT_CONSTRUCT_CALL_BADALLOC) {
        return _objc_callBadAllocHandler(cls);
    }
    return nil;
}

继续看下面代码

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);
}


inline void 
objc_object::initInstanceIsa(Class cls, bool hasCxxDtor)
{
    ASSERT(!cls->instancesRequireRawIsa());
    ASSERT(hasCxxDtor == cls->hasCxxDtor());

    initIsa(cls, true, hasCxxDtor);
}


inline void 
objc_object::initIsa(Class cls)
{
    initIsa(cls, false, false);
}

我们在if之前打印 obj

image.png

在执行完 initIsa 之后再打印 obj

image.png

可见 initIsa 方法把实例与分配的内存建立了联系。

inline void 
objc_object::initIsa(Class cls)
{
    initIsa(cls, false, false);
}

inline void 
objc_object::initIsa(Class cls, bool nonpointer, UNUSED_WITHOUT_INDEXED_ISA_AND_DTOR_BIT bool hasCxxDtor)
{ 
    ASSERT(!isTaggedPointer()); 

    isa_t newisa(0);

    if (!nonpointer) {
        newisa.setClass(cls, this);
    } else {
        ASSERT(!DisableNonpointerIsa);
        ASSERT(!cls->instancesRequireRawIsa());


#if SUPPORT_INDEXED_ISA
        ASSERT(cls->classArrayIndex() > 0);
        newisa.bits = ISA_INDEX_MAGIC_VALUE;
        // isa.magic is part of ISA_MAGIC_VALUE
        // isa.nonpointer is part of ISA_MAGIC_VALUE
        newisa.has_cxx_dtor = hasCxxDtor;
        newisa.indexcls = (uintptr_t)cls->classArrayIndex();
#else
        newisa.bits = ISA_MAGIC_VALUE;
        // isa.magic is part of ISA_MAGIC_VALUE
        // isa.nonpointer is part of ISA_MAGIC_VALUE
#   if ISA_HAS_CXX_DTOR_BIT
        newisa.has_cxx_dtor = hasCxxDtor;
#   endif
        newisa.setClass(cls, this);
#endif
        newisa.extra_rc = 1;
    }

    // This write must be performed in a single store in some cases
    // (for example when realizing a class because other threads
    // may simultaneously try to use the class).
    // fixme use atomics here to guarantee single-store and to
    // guarantee memory order w.r.t. the class index table
    // ...but not too atomic because we don't want to hurt instantiation
    isa = newisa;
}

initIsa 方法中我们可以找到

isa_t newisa(0);
isa = newisa;

所以 isa_t 就是 isa 的类型。

后续会有单独的篇章来对 isa 进行探索

字节对齐计算

对象的内存对齐

以64位设备的8字节对齐为例 (对齐是不足的补满)

define WORD_MASK 7UL
static inline uint32_t word_align(uint32_t x) {
    return (x + WORD_MASK) & ~WORD_MASK;
}

如果我们自己实现一个8字节对齐的话,可以这样写

func aligned(x: Int) {
    return (x + 7)/8*8
}

而源码中的 & ~WORD_MASK , 实际上的作用与我们的 /8*8 是一致的,只不过位运算比我们的乘除法效率要高很多。

我们通过对 Int 型进行 /8 ,把原值中不足 8 的余数舍弃,然后 *8 , 还原为 8 的整数倍。其实就相当于减去余数。而位运算就是直接减去了余数。

首先:define WORD_MASK 7UL (UL表示这是无符号长整型)
写为二进制:
WORD_MASK    00000000 00000111
取反:
~WORD_MASK   11111111 11111000 
~WORD_MASK和x进行 & 则会把x的二进制中的后3为置0,而这3位中存放的正是不满8的余数部分
8的二进制:    00000000 00001000

/* 上面的二进制都只写了后8位 */

通过上面分析,我们可以知道,去除不足8的余数,其实就是把二进制的后3位清0,那么其实我们也可以这样写

func aligned(x: Int) {
    return (x + 7) >> 3 << 3
}
与苹果源码相比,如果要同样兼容64位和32位,我们的 73 就需要2个宏来定义,而源码中只需要1个宏

结构体的内存对齐方式

数据大小

数据类型64位字节大小32位字节大小
bool
char
unsigned char
BOOL
int8_t
Boolean
11
short
unsigned short
int16_t
unichar
22
int
int32_t
unsigned int
float
boolean_t
44
long
unsigned long
NSInteger
NUSInteger
CGFloat
84
long long
double
int64_t
88

结构体内存对齐规则

  1. 结构体( struct )的第一个数据成员从 offset 为0的地方开始放置,后续每个数据成员存储的起始位置都要从该成员大小或者成员的子成员的整数倍处开始 (比如 int 大小为4字节,则要从4的整数倍地址开始存储)
  2. 如果一个结构体里包含结构体作为成员,则结构体成员要从其内部最大成员的整数倍地址开始存储 ( struct a 里有成员 struct b, b 里有 char, int, double 成员,那么 b 应该从 double 大小 ,也就是8的整数倍开始存储)
  3. 结构体的总大小,也就是 sizeof 的结果必须是其内部最大成员的整数倍,不足的要补齐
先看基础数据类型结构体的例子
struct StructA {
    double a;   // 8 [0~7]
    int b;      // 4 [8~11]
    char c;     // 1 [12]
    short d;    // 2 [14~15]
} structa;      // 15 8的倍数 16

struct StructB {
    double a;   // 8 [0~7]
    char b;     // 1 [8]
    int c;      // 4 [12~15]
    short d;    // 2 [16~17]
} structb;      // 17 8的倍数 24

NSLog(@"StructA size: %lu", sizeof(structa));
NSLog(@"StructB size: %lu", sizeof(structb));

image.png 可见结构体成员不同排列顺序,对结构体大小是有影响的。

嵌套结构体
struct StructC {
    double a;           // 8 [0~7]
    int b;              // 4 [8~11]
    char c;             // 1 [12]
    short d;            // 2 [14~15]
    float e;            // 4 [16~19]
    struct StructA f;   // 15 8的倍数开始 24
} structc;              // 39 8的倍数 40

struct StructA {
    double a;   // 8 [24~31]
    int b;      // 4 [32~35]
    char c;     // 1 [36]
    short d;    // 2 [38~39]
} structa;

NSLog(@"StructC size: %lu", sizeof(structc));

image.png

struct StructC {
    double a;           // 8 [0~7]
    char b;             // 1 [8]
    int c;              // 4 [12~15]
    short d;            // 4 [16~19]
    struct StructB f;   // 19 8的倍数开始 24
} structc;              // 41 8的倍数 48

struct StructB {
    double a;   // 8 [24~31]
    char b;     // 1 [32]
    int c;      // 4 [36~39]
    short d;    // 2 [40~41]
} structb;

NSLog(@"StructC size: %lu", sizeof(structc));

image.png

struct StructC {
    int b;              // 4 [0~3]
    char c;             // 1 [4]
    short d;            // 2 [6~7]
    float e;            // 4 [8~11]
    struct StructA f;   // 11 8的倍数开始 16
} structc;              // 28 8的倍数 32

struct StructA {
    double a;   // 8 [16~23]
    int b;      // 4 [24~27]
    char c;     // 1 [28]
} structa;

NSLog(@"StructC size: %lu", sizeof(structc));

image.png