美文网首页
OC对象探究02:内存分析

OC对象探究02:内存分析

作者: 开发狗 | 来源:发表于2020-10-10 16:06 被阅读0次

1.对象的内存地址分布

首先创建一个Person类,其中包含了namenicknameagec1c2五个属性。此时创建一个person对象,并对其属性进行赋值,然后打印person对象的内存地址。

WechatIMG19.jpeg person对象.png
通常在调试窗口使用p person 命令打印对象person的内存地址。使用x/4gxx/8gx + 内存地址$11,(其中$11为图中person 类的标识符 )可以来打印具体的地址下的内存地址情况,如图。
  • 在使用x/8gx打印对象内存地址的结果中,冒号前面的是从对象的首地址,冒号后是其属性的值,如果属性未被赋值时,为0x0000000000000000
  • 标识10x0000600002d6fd20地址是指对象person的首地址;
  • 标识2的是当前person对象的isa指针,指向的是类;
  • 标识3包含了agec1c2三个属性的值,在OC中为了节约内存空间将基本数据类型的数据可以放在一条数据内存中
  • 开辟内存时进行了内存重排,从图中0x00000012000062610x000000010e0c60380x000000010e0c6058 前的原因

2.结构体的内存对齐

通过查看源码可以看出,OC对象的内存对齐来自于结构体的内存,所以研究结构体。结构体指针的大小为8字节,结构体的内存大小取决于其内部属性。

struct Struct1 {
    double a;   // 8  0 1 2 3 4 5 6 7
    char b;     // 1  8
    int c;      // 4  12 13 14 15
    short d;    // 2  16 17    总计需要:17   总计开辟:24
} struct1;

struct Struct2 {
    double a;   // 8  0 1 2 3 4 5 6 7
    int b;      // 4  8 9 10 11
    char c;     // 1  12
    short d;    // 2  14 15      总计需要:15   总计开辟:16
    struct Struct1 struct1;
} struct2;

打印如下:

2020-10-02 11:52:16.814642+0800 内存对齐[2209:74659] 24-40

首先分析结构体struct1
在64位架构下,按照定义计数从0位开始

  • double a8个字节则为(0,1,2,3,4,5,6,7)8
  • char b1个字节,根据连续性原则 b会从第8位开始,所以b占用第8位;
  • int c4个字节,根据内存对齐的原则,当前属性的开始位是其所占内存大小的整数倍,而b占用了第8位,第9位不是4的倍数,所以需要向后移动直到第12位,占用了 (12,13,14,15)
  • short d2个字节,根据当前属性的开始位是其所占内存大小的整数倍原则,d的占用了(16,17)
  • struct1实际占用内存17字节,根据结构体开辟内存大小是其属性最大值的整数倍,并且要满足大于或等于实际需要大小,因此struct1的内存大小为24

对于结构体struct2来说:

  • double a8个字节则为(0,1,2,3,4,5,6,7)8
  • int b4个字节,根据内存对齐的原则,当前属性的开始位是其所占内存大小的整数倍,所以b占用了(8,9,10,11)
  • char c1个字节,所以占用了第12
  • short d2个字节,根据当前属性的开始位是其所占内存大小的整数倍原则,d占用了(14,15)位
  • 由上面的struct1得到struct1的内存大小为24字节,根据如果结构体中有结构体作为属性时,该结构体的起始位需要是该结构体中最大属性值得整数倍,此时struct1正好从16开始,并占用了24字节
  • 由上可得出struct2的内存大小为40

3. instanceSize方法分析

instanceSize方法是在初始化alloc方法中的方法_class_createInstanceFromZone中调用的,用来计算对象开辟的内存大小。首先通过宏定义得fastPath()来判断是否有较大概率进行快速路径,此时使用的是gcc中定义的__builtin_except(N, 1)表示较大概率执行,此时返回调用cache中的方法fastInstanceSize来计算内存大小,在fastInstanceSize中会走计算size_t size = _flags & FAST_CACHE_ALLOC_MASK通过按位与的计算方式将_flags与宏定义的FAST_CACHE_ALLOC_MASK 0x1ff8计算得出内存大小,然后调用align16方法,在align16方法中使用(x+size_t(15) & ~size_t(15))取16位以下为0的方式,进行了按照16位的倍数的内存对齐

  • _flagschass_rw_t结构体中的属性,在初始化alloc方法时系统回调用objc_class结构体中的setInfo方法,并调用data->setFlags 方法为_flags进行了赋值
  • x+size_t(15) & ~size_t(15): 将x与15相加,然后再和15按位取反的数进行按位与计算,示例如下, 令 x = 7:
x  = 7    0000 0111
15        0000 1111
x + 15    0001 0110
~15       1111 0000

x + size_t(15) & ~size_t(15)
16        0001 0000

可以看出该算法是开辟的内存使用实际开辟的内存进行16的倍数扩容。

4. calloc 分析

calloc调用流程.png
  • malloc_zone_calloc 中返回的是void * 类型的指针ptr,所以在该方法中重点为ptr的赋值部分ptr = zone->calloc(zone, num_items, size),在该方法中进入calloc又会产生递归,通过研究此处实际调用default_zone_calloc方法。
  • default_zone_callocreturn zone->calloc(zone, num_items, size) 此时又调用了一次zonecalloc方法,在实际当中调用的则为nano_malloc方法
  • nano_malloc方法中
static void *nano_malloc(nanozone_t *nanozone, size_t size)
{
    if (size <= NANO_MAX_SIZE) {
        void *p = _nano_malloc_check_clear(nanozone, size, 0);
        if (p) {
            return p;
        } else {
            /* FALLTHROUGH to helper zone */
        }
    }
    malloc_zone_t *zone = (malloc_zone_t *)(nanozone->helper_zone);
    return zone->malloc(zone, size);
}

重点是调用了_nano_malloc_check_clear方法进行对p指针进行赋值,在初始化时size 为 0,一定是小于NANO_MAX_SIZE = 256的,所以此处一定会返回p

  • _nano_malloc_check_clear方法中,重点在如下语句中
void *ptr;
size_t slot_key;
size_t slot_bytes = segregated_size_to_fit(nanozone, size, &slot_key); // Note slot_key is set here
mag_index_t mag_index = nano_mag_index(nanozone);
nano_meta_admin_t pMeta = &(nanozone->meta_data[mag_index][slot_key]);
ptr = OSAtomicDequeue(&(pMeta->slot_LIFO), offsetof(struct chained_block_s, next));

所以重点在一个slot_bytes的计算,然后对 ptr的赋值,而slot_bytes的计算是重点,接下来研究segregated_size_to_fit方法

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

在代码中 NANO_REGIME_QUANTA_SIZE(1 << SHIFT_NANO_QUANTUM)也就是16SHIFT_NANO_QUANTUM4,所以在初始化时,通过size = NANO_REGIME_QUANTA_SIZEsize赋值为16(size + NANO_REGIME_QUANTA_SIZE -1) >> SHIFT_NANO_QUANTUM 是将低四位进行抹零,slot_bytes = k << SHIFT_NAMO_QUANTUM 将k进行左移四位,通过这两步将高位保留低位抹零,从而保证为16的倍数。

相关文章

网友评论

      本文标题:OC对象探究02:内存分析

      本文链接:https://www.haomeiwen.com/subject/pfxjuktx.html