# 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. 流程图

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

![](/files/-MbdPAYhC161SvwcXTGA)

## 三、字节对齐

### 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](/files/-MbX2USHNe679UfBDOfM)

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

### 3.3 内存调试

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

![7](/files/-MbX2USIWoSlokXhKW9U)

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

![](/files/-MbX2USJe1A4T592PQYk)

* 这里发现 `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;` 会是怎样呢？


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://ryukiedev.gitbook.io/wiki/ios/di-ceng/01.alloc-yu-zi-jie-dui-qi.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
