[OC底层]malloc内存分析

1,641 阅读3分钟

本篇文章通过一个小例子过一下malloc的源码

首先我们建立一个person类,对应建立几个属性,然后执行对sizeof()class_getInstanceSize(),malloc_size()的打印。

Person *person = [Person alloc];
person.name      = @"ErBao";
person.nickName  = @"EB";

NSLog(@"sizeof(persion): %lu", sizeof(person)); 
NSLog(@"class_getInstanceSize([Person class]): %lu", class_getInstanceSize([Person class]));
NSLog(@"malloc_size((__bridge const void *)(person)): %lu", malloc_size((__bridge const void *)(person)));

打印出来的结果是:

  • sizeof(persion): 8
  • class_getInstanceSize([LGPerson class]): 40
  • malloc_size((__bridge const void *)(person)): 48

解析:

  • sizeof(person)中的person是指针, 一个指针占8个字节, 所以sizeof(person) 为8
  • class_getInstanceSize 采用8字节对齐,类实例的字节大小,各属性所占内存大小如下图8+8+4+8=28,加上isa指针的8字节后为36, 按内存对齐原则,总大小须是最长属性大小整数倍,所以结果为40 下面我们分析一下,为什么malloc_size((__bridge const void *)(person))等于48
  • malloc的源码位于libMalloc库中,我们先下载好源码opensource.apple.com/tarballs/li… 首先用calloc在内存的动态存储区中分配1块长度为40字节的连续区域 void *p = calloc(1, 40); 点击calloc 调用如下源码:

Screenshot 2021-10-02 at 21.09.51.png

然后我们进入到_malloc_zone_calloc这个方法.

Screenshot 2021-10-02 at 21.12.23.png 这时候点击calloc只有声明, 无法看到函数调用.这个时候我们在console里面执行po zone->calloc

Screenshot 2021-10-02 at 21.24.24.png

然后我们根据输出结果找到实现的部分

Screenshot 2021-10-02 at 21.27.21.png 可以看出在default_zone_calloc 里面又执行了zone->calloc, 然后再来po一下.

Screenshot 2021-10-02 at 21.31.58.png 找到nano_calloc方法

Screenshot 2021-10-03 at 09.29.33.png _nano_malloc_check_clear推测可能开辟内存的大小 点进去查看(方法有点长,截图可能看的不清楚,我就直接粘贴代码段了):

**static** **void** *

_nano_malloc_check_clear(nanozone_t *nanozone, size_t size, boolean_t cleared_requested)
{

    MALLOC_TRACE(TRACE_nano_malloc, (uintptr_t)nanozone, size, cleared_requested, 0);

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

    if (ptr) {
        unsigned debug_flags = nanozone->debug_flags;
#if NANO_FREE_DEQUEUE_DILIGENCE
        size_t gotSize;
        nano_blk_addr_t p; // the compiler holds this in a register
        p.addr = (uint64_t)ptr; // Begin the dissection of ptr
        if (NANOZONE_SIGNATURE != p.fields.nano_signature) {
            malloc_zone_error(debug_flags, true, "Invalid signature for pointer %p dequeued from free list\n", ptr);
        }
        if (mag_index != p.fields.nano_mag_index) {
            malloc_zone_error(debug_flags, true, "Mismatched magazine for pointer %p dequeued from free list\n", ptr);
        }
        gotSize = _nano_vet_and_size_of_free(nanozone, ptr);
        if (0 == gotSize) {
            malloc_zone_error(debug_flags, true, "Invalid pointer %p dequeued from free list\n", ptr);
        }
        if (gotSize != slot_bytes) {
            malloc_zone_error(debug_flags, true, "Mismatched size for pointer %p dequeued from free list\n", ptr);
        }
        if (!_nano_block_has_canary_value(nanozone, ptr)) {
            malloc_zone_error(debug_flags, true, "Heap corruption detected, free list canary is damaged for %p\n", "*** Incorrect guard value: %lu\n", ptr ((chained_block_t)ptr)->double_free_guard);
        }
#if defined(DEBUG)
        void *next = (void *)(((chained_block_t)ptr)->next);
        if (next) {
            p.addr = (uint64_t)next; // Begin the dissection of next
            if (NANOZONE_SIGNATURE != p.fields.nano_signature) {
            malloc_zone_error(debug_flags, true, "Invalid next signature for pointer %p dequeued from free " "list, next = %p\n", ptr, "next");
         }

        if (mag_index != p.fields.nano_mag_index) {
            malloc_zone_error(debug_flags, true, "Mismatched next magazine for pointer %p dequeued from " "free list, next = %p\n", ptr, next);
        }
        gotSize = _nano_vet_and_size_of_free(nanozone, next);
        
        if (0 == gotSize) {
            malloc_zone_error(debug_flags, true, "Invalid next for pointer %p dequeued from free list", "next = %p\n", ptr, next);
        }
        if (gotSize != slot_bytes) {
            malloc_zone_error(debug_flags, true, "Mismatched next size for pointer %p dequeued from free, "list, next = %p\n", ptr, next);
        }
    }
#endif /* DEBUG */

#endif /* NANO_FREE_DEQUEUE_DILIGENCE */
    ((chained_block_t)ptr)->double_free_guard = 0;
    ((chained_block_t)ptr)->next = **NULL**; // clear out next pointer to protect free list    
    } else {
        ptr = segregated_next_block(nanozone, pMeta, slot_bytes, mag_index);
    }
    if (cleared_requested && ptr) {
        memset(ptr, 0, slot_bytes); // **TODO: Needs a memory barrier after memset to ensure zeroes land first?**
    }
        return ptr;
    }

其中ptr = OSAtomicDequeue(&(pMeta->slot_LIFO), offsetof(**struct** chained_block_s, next)); 我们看到了LIFO推测先进后出,所以ptr在栈区,将堆内存地址赋值给ptr. segregated_next_block的底层是do while循环,在内存中查找可开辟的地址

Screenshot 2021-10-03 at 10.23.07.png #define SHIFT_NANO_QUANTUM 4. 在segregated_size_to_fit里先右移4位, 再左移4位.进行16字节对齐.所以上面malloc_size(person)48 Screenshot 2021-10-03 at 10.24.56.png

总结一下:

  • 开辟的内存也就是对象的内存, 以16字节对齐
  • 对象的成员变量也就是结构体内部, 以8字节对齐
  • 对象和对象之间在内存中是16字节对齐 为什么对象中要以16字节对齐呢? 我们来简单的分析一下:
先说结论:

8字节发生错误的概率更大. 对象在内存中存储的示意图如下: Screenshot 2021-10-03 at 11.37.03.png 任何对象都至少包括一个8字节的isa,然后加上还需要给成员变量分配空间,就算成员变量就占2个字节的空间,那么这个对象最后所占的空间为8(isa)+2(成员变量)+4(对齐) = 16字节. 一个对象至少都要占16个字节, 如果读取的时候以8个字节为一组来读取,那么计算的次数会增大, 发生错误的概率也会增大. 那为什么不用32位对齐呢?因为这样很多时候会导致空间的浪费.

附图

基本数据类型在内存中所占空间大小:

Screenshot 2021-10-03 at 11.42.40.png

Reference:

juejin.cn/post/697159…