# 01.alloc与字节对齐

## 一、探索alloc

看下面的例子，输出会是什么样的呢？

```cpp
Ryukie *obj1 = [Ryukie alloc];
Ryukie *obj2 = [obj1 init];
Ryukie *obj3 = [obj1 init];

NSLog(@"Objc1：%p，他的指针：%p", obj1, &obj1);
NSLog(@"Objc2：%p，他的指针：%p", obj2, &obj2);
NSLog(@"Objc3：%p，他的指针：%p", obj3, &obj3);
```

输出如下：

```
2021-06-06 19:56:22.045515+0800 OCObject[10944:3914848] Objc1：0x28220bc60，他的指针：0x16d421bf8
2021-06-06 19:56:22.045611+0800 OCObject[10944:3914848] Objc2：0x28220bc60，他的指针：0x16d421bf0
2021-06-06 19:56:22.045659+0800 OCObject[10944:3914848] Objc3：0x28220bc60，他的指针：0x16d421be8
```

* 不同的指针，指向了同样的内存空间

## 二、通过源码了解alloc实现

> [Source Browser](https://opensource.apple.com/tarballs/objc4/)
>
> [Apple Open Source](https://opensource.apple.com)

`NSObject.mm`这里就是`NSobject`的核心实现代码

### 2.1 可运行调试的源码

> [GitHub](https://github.com/LGCooci/objc4_debug)

将项目Clone下来，即可运行调试。更加直观的学习底层实现。

### 2.2 调用流程探究

#### a. 实验代码

我们在main中写下如下代码，分别在<-处添加断点

```cpp
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        NSLog(@"Hello, World!");
        RYModel *father = [RYModel alloc];<-

        RYModel *objc = [RYModel alloc];<-    
    }
    return 0;
}
```

#### b. alloc

现在是`RYModel`第一次调用alloc。字面实现如下：

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

按照代码下一步应该会调用`_objc_rootAlloc`，真的吗？

#### c. objc\_alloc

我们发现断点走到了这里，正如注释里面所说：`Calls [cls alloc].`

```cpp
// Calls [cls alloc].
id
objc_alloc(Class cls)
{
    return callAlloc(cls, true/*checkNil*/, false/*allocWithZone*/);
}
```

我们推断，这里应该是alloc的实现IMP被修改了。继续下一步

#### d. callAlloc

断点直接来到了 `objc_msgSend` 处，又继续调用了 `alloc`

```cpp
// Call [cls alloc] or [cls allocWithZone:nil], with appropriate
// shortcutting optimizations.
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));
}
```

#### e. \_objc\_rootAlloc

这次断点才来到了，`_objc_rootAlloc`

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

#### f. \_objc\_rootAllocWithZone

又来到了`callAlloc`方法中，这次断点来到了`_objc_rootAllocWithZone`

```cpp
static ALWAYS_INLINE id
callAlloc(Class cls, bool checkNil, bool allocWithZone=false) {
#if __OBJC2__
    ...
    if (fastpath(!cls->ISA()->hasCustomAWZ())) { // 这里会判断类对象的isa指针是否有自定义的AllocWithZone的实现
        return _objc_rootAllocWithZone(cls, nil); <-
    }
#endif
    ...
}
```

`_objc_rootAllocWithZone`的实现：

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

我们继续下一步

#### g. \_class\_createInstanceFromZone 创建实例

```cpp
/***********************************************************************
* class_createInstance
* fixme
* Locking: none
*
* Note: this function has been carefully written so that the fastpath
* takes no branch.
**********************************************************************/
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);// 1. 计算所需大小
    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); // 2. 开辟空间
    }
    if (slowpath(!obj)) {
        if (construct_flags & OBJECT_CONSTRUCT_CALL_BADALLOC) {
            return _objc_callBadAllocHandler(cls);
        }
        return nil;
    }

    if (!zone && fast) {
        obj->initInstanceIsa(cls, hasCxxDtor); // 3. 实例化isa
    } 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);
}
```

**g.1 instanceSize 大小**

* 这里会去缓存中查找
* `CoreFoundation`规定最小16个字节
  * 有`isa`指针的8字节
  * 还有8字节预留
* `// Class's ivar size rounded up to a pointer-size boundary.`
  * 成员变量的大小向上取整（一个指针的大小8字节）
  * 即：成员变量的字节对齐
  * 即：不足8字节就按8字节
* `// May be unaligned depending on class's ivars.`
  * 大小取决于成员变量的大小

```cpp
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;
}

// Class's ivar size rounded up to a pointer-size boundary.
uint32_t alignedInstanceSize() const {
    return word_align(unalignedInstanceSize());
}

// May be unaligned depending on class's ivars.
uint32_t unalignedInstanceSize() const {
    ASSERT(isRealized());
    return data()->ro()->instanceSize;
}
```

**g.2 calloc 开空间**

这里分配的还空间内还有数据，因为内存只覆写，不删除。

```cpp
if (zone) {
    obj = (id)malloc_zone_calloc((malloc_zone_t *)zone, 1, size);// OBJC2 zone已经不用了，不走这里
} else {
    obj = (id)calloc(1, size);
}
```

**g.3 initInstanceIsa 初始化实例isa**

这里会将刚创建的obj和isa进行绑定，完成这一步后，可以发现，obj就会先变成`RYModel`了。

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

    initIsa(cls, true, hasCxxDtor);
}
```

#### h. 非第一次alloc

* 发现&#x5728;***d. callAlloc***&#x8FD9;一步中有所不同：
* 没有走`return ((id(*)(id, SEL))objc_msgSend)(cls, @selector(alloc));`
* 而是直接走了`_objc_rootAllocWithZone`

> 这里是LLVM做了处理，对alloc的IMP做了调整，在类第一次调用alloc时进行了一些处理，然后再调用原有的alloc实现。

#### i. 流程图

> 图片较大较长，建议下载到本地

![](https://4193904735-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MI8JgbGh3U6X_oedqkm%2Fsync%2F1ec2a60a29d43e02a0ecf74e96dfecc65b6824a1.png?generation=1623118629665647\&alt=media)

## 三、字节对齐

### 3.1 算法

```cpp
// Class's ivar size rounded up to a pointer-size boundary.
uint32_t alignedInstanceSize() const {
    return word_align(unalignedInstanceSize());
}

static inline uint32_t word_align(uint32_t x) {
    return (x + WORD_MASK) & ~WORD_MASK;
}
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);
}

#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
```

### 3.2 先思考一个问题：

![6](https://4193904735-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MI8JgbGh3U6X_oedqkm%2Fsync%2Fe34595f1e6c45700e8302497d529b6cacba8ca4d.png?generation=1622995238196094\&alt=media)

* 显然第二种效率更高，但是牺牲了一定的空间。
  * 这就是典型的`以空间，换时间`，对于CPU来说处理速度非常重要

### 3.3 内存调试

* 我们在第一个demo中下个断点

![7](https://4193904735-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MI8JgbGh3U6X_oedqkm%2Fsync%2F168af2a462981ebae07b846bc539784118dc65b5.png?generation=1622995237863950\&alt=media)

```cpp
(lldb) x obj1
0x282d86140: 0d 57 41 04 a1 61 00 00 28 00 41 04 01 00 00 00  .WA..a..(.A.....
0x282d86150: 42 00 00 00 00 00 00 00 40 61 d8 82 02 00 00 00  B.......@a......
(lldb) p 0x000061a10441570d // 小端、反着看
(long) $3 = 107344189019917
```

* `(long) $1 = 80361110149439809`这里其实是isa指针，但是打印出来确实long类型？

### 3.4 isa 与 MASK

#### a.获取isa指针

```cpp
// Get the class pointer out of an isa. When ptrauth is supported,
// this operation is optionally authenticated. Many code paths don't
// need the authentication, so it can be skipped in those cases for
// better performance.
//
// Note: this method does not support retrieving indexed isas. When
// indexed isas are in use, it can only be used to retrieve the class
// of a raw isa.
#if SUPPORT_INDEXED_ISA || (ISA_SIGNING_AUTH_MODE != ISA_SIGNING_AUTH)
#define MAYBE_UNUSED_AUTHENTICATED_PARAM __attribute__((unused))
#else
#define MAYBE_UNUSED_AUTHENTICATED_PARAM UNUSED_WITHOUT_PTRAUTH
#endif

inline Class
isa_t::getClass(MAYBE_UNUSED_AUTHENTICATED_PARAM bool authenticated) {
#if SUPPORT_INDEXED_ISA
    return cls;
#else

    uintptr_t clsbits = bits;

#   if __has_feature(ptrauth_calls)
#       if ISA_SIGNING_AUTH_MODE == ISA_SIGNING_AUTH
    // Most callers aren't security critical, so skip the
    // authentication unless they ask for it. Message sending and
    // cache filling are protected by the auth code in msgSend.
    if (authenticated) {
        // Mask off all bits besides the class pointer and signature.
        clsbits &= ISA_MASK;
        if (clsbits == 0)
            return Nil;
        clsbits = (uintptr_t)ptrauth_auth_data((void *)clsbits, ISA_SIGNING_KEY, ptrauth_blend_discriminator(this, ISA_SIGNING_DISCRIMINATOR));
    } else {
        // If not authenticating, strip using the precomputed class mask.
        clsbits &= objc_debug_isa_class_mask;
    }
#       else
    // If not authenticating, strip using the precomputed class mask.
    clsbits &= objc_debug_isa_class_mask;
#       endif

#   else
    clsbits &= ISA_MASK;
#   endif

    return (Class)clsbits;
#endif
}
```

这里要拿到isa需要&一个mask，在isa.h中可以找到对应架构的定义

#### b.获取MASk

> 下面是`ARM64`的，其他架构的可以自行在头文件中查找，很简单的

```cpp
#     define ISA_MASK        0x0000000ffffffff8ULL
```

#### c.复原出isa

再次输出

```cpp
(lldb) po 0x000061a10441570d & 0x0000000ffffffff8
Ryukie
```

成功输出了Class: Ryukie

### 3.5 查看真实的内存排列方式

```cpp
@interface Ryukie : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) int age;
@property (nonatomic, assign) BOOL isboy;
@property (nonatomic, copy) NSString *desc;
@end
```

#### a.一个内存对齐的场景

* 首先我们格式化打印分析 x/4gx&#x20;
* 这种方式更加直观，不用我们自己去按小端的方式反着看了。
  * `4`代表打印4个单位的，可以按需要调整。

```cpp
(lldb) x/4gx obj1
0x28150e2e0: 0x000041a100c0577d 0x0000001200000001
0x28150e2f0: 0x0000000100c00008 0x0000000100c00028
```

![](https://4193904735-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MI8JgbGh3U6X_oedqkm%2Fsync%2F72c8aa4b538627d75dc73e72b89ab0ace6822efa.png?generation=1622995238781779\&alt=media)

* 这里发现 `po 0x0000001200000001` 结果很怪异 `77309411329`
* 这里我们还剩 `age` & `isboy` 两个成员变量没有输出
* 将这里的16位分开看下
  * `po 0x00000012`
    * 18
  * `po 0x00000001`
    * 1
    * 这里Bool只占一个字节但是拉伸为了8个字节
* 这里就是比较明显的内存对齐的一个场景

#### b.思考

> 如果我们将上面的`@property (nonatomic, assign) int age;`改为`@property (nonatomic, assign) NSInteger age;` 会是怎样呢？
