IOS底层(四): alloc相关: 对象属性在内存中的布局

OC底层源码/原理合集

建议先看下 IOS底层(三): alloc相关1.初探 alloc, init, new源码分析

之前有些可能不太理解, 为什么需要8字节存储, 直接按本身字节数存不就好了吗? 例如内存条这样存放

错误例子

存完之后读取, 假如第一个是4字节, 第二个是8字节, 第三个是8字节, 第四个是4字节...
当CPU开始读数据时候读完第一个4字节, 立马要变换读取长度8字节, 当读到第四个时候又要变化读取长度4字节读取, 这样每次都要做判断改变读取长度, 繁琐且会影响CPU速度。CPU意思: 大哥你别这样存了, 我难受!!!

因为通常存储指针特别多, 即8字节比较多, 就规定存储时候都是8字节为一段进行存储, 不足也为8, 以空间换取时间。

接下来, 我们验证下是否真的是按我们想象的一样8字节对齐

建立一个类, 并建立一些属性

@interface SATest : NSObject

@property (nonatomic, strong) NSString *name;
@property (nonatomic, assign) int age;
@property (nonatomic, assign) long height;
@property (nonatomic, strong) NSString *hobby;
#import 
#import 
#import 
#import "SATest.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        SATest *test = [[SATest alloc] init];
        test.name = @"ShawnAlex";
        test.age = 18;
        test.height = 180;
        test.hobby = @"女";
        
        NSLog(@"%p", test);
    }
    return 0;
}

加个断点, 之后x一下, 看下内存段
x: 以16进制打印对象地址空间

x test
读取1.1

先po一下前面指针, 确保的确读的是SATest(前面第一个是栈顶指针起始位置)

读取1.2

然后我们8个一取 po 读一下, 因为ios是小端模式(强制转换数据不需要调整字节内容)地址要倒着读, 即从右往左读, 那么我们取8位从右往左读一下, 可看到

读取1.3

看一下的确是能读取出来, 这也验证了的确是以8字节进行存储

其中有个问题? 第一个内存是什么?为什么读成这样子

SATest继承于NSObject, 而NSObject 创建时候有个默认的isa

@interface NSObject  {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-interface-ivars"
    Class isa  OBJC_ISA_AVAILABILITY;
#pragma clang diagnostic pop
}
读取1.4

这样读取是不是不方便, 那么我们x/4xg 先转换下, 再读取

例1.5

x/4xg意思是 以16进制4个片段一读取, 当然我们发现最后一个怎么没有读取出来呢?

x/5xg一下可以看到, 读取出来了

读取1.6

附: 怎么看到全部内存段?

断点 → Debug WorkflowView Memory 输入 即可看到代码段

ex1

address 处读一下栈顶指针, 也可以看到内存段


ex2

我们接下来再看一个这个例子:

ex3

可看到, 自带isa8字节存储, 而系统实际分配由于16字节对齐, 所以开辟了16字节的内存空间

那么比如: 我带一些属性呢? 会怎么开辟内存空间?

ex4
ex5
ex6

可那看到计算内存空间大小这里, 开辟的是32

isa: 8, 字符串name: 8, 字符串nickname: 8
8+8+8 = 24, 16字节对齐, 32返回, 所以这里size是32

其实有些时候有些疑问, 一会是8字节对齐? 一会是16字节对齐? 到底按照哪个对齐?

对于一个对象, 对齐方式是8字节对齐, 具体可以看class_getInstanceSize源码

class_getInstanceSize

size_t class_getInstanceSize(Class cls)
{
    if (!cls) return 0;
    return cls->alignedInstanceSize();
}
    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;
}

苹果为了防止一切的容错, 采用的是16字节对齐的内存, 主要是因为采用8字节对齐时, 两个对象的内存会紧挨着, 显得比较紧凑, 而16字节比较宽松, 利于苹果以后的扩展。

malloc_size

extern size_t malloc_size(const void *ptr);

malloc_size是采用16字节对齐, 对象实际分配的内存大小必须是16的整数倍

#define SHIFT_NANO_QUANTUM      4
#define NANO_REGIME_QUANTA_SIZE (1 << SHIFT_NANO_QUANTUM)   // 16

static MALLOC_INLINE size_t
segregated_size_to_fit(nanozone_t *nanozone, size_t size, size_t *pKey)
{
    size_t k, slot_bytes;

    if (0 == size) {
        size = NANO_REGIME_QUANTA_SIZE; // Historical behavior
    }
    k = (size + NANO_REGIME_QUANTA_SIZE - 1) >> SHIFT_NANO_QUANTUM; // round up and shift for number of quanta
    slot_bytes = k << SHIFT_NANO_QUANTUM;                           // multiply by power of two quanta size
    *pKey = k - 1;                                                  // Zero-based!

    return slot_bytes;
}

算法原理:k + 15 >> 4 << 4 ,其中 右移4 + 左移4相当于将后4位抹零,跟 k/16 * 16一样 ,是16字节对齐算法,小于16就成0了

你可能感兴趣的:(IOS底层(四): alloc相关: 对象属性在内存中的布局)