iOS 通過源碼看看alloc以及內(nèi)存分配

首先從源碼(基于779.1)調(diào)試追蹤一下alloc的流程。

image.png

大致如上圖所示
接下來從一道經(jīng)典面試題開個頭
一個NSObject對象占用多少內(nèi)存
答:系統(tǒng)分配16字節(jié),實際利用8字節(jié)

  • NSObject 只有一個成員變量 isa指針 ,arm64架構(gòu)后 可以追尋到一個isa_t類型的聯(lián)合體(union)結(jié)構(gòu)
union isa_t {
    isa_t() { }
    isa_t(uintptr_t value) : bits(value) { }

    Class cls;
    uintptr_t bits;
#if defined(ISA_BITFIELD)
    struct {
        ISA_BITFIELD;  // defined in isa.h
    };
#endif
};

絕大多數(shù)情況下,蘋果采用了優(yōu)化的isa策略,即isa_t類型不是class 而是struct

image.png

根據(jù)它的位域來看 占用64位,即8字節(jié)。各位的含義這里就先不說了
在64位里指針占8字節(jié),看起來一個NSObject對象 8字節(jié) 就行了,而不是16字節(jié),這就需要繼續(xù)探索了。

回到上面的alloc流程圖

我們主要來看看·cls->instanceSize(計算開辟內(nèi)存大小) 和(id)calloc(1, size) (申請開辟內(nèi)存,返回地址指針)

先看看cls->instanceSize 相關(guān)的源碼
size_t instanceSize(size_t extraBytes) const {
        if (fastpath(cache.hasFastInstanceSize(extraBytes))) {
            ///都走快速計算 ,在runtime入口函數(shù)里,`map_images`內(nèi)的`realizeClassWithoutSwift` 的`setInstanceSize` 做cache的setFastInstanceSize
            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;
    }
 size_t fastInstanceSize(size_t extra) const
    {
       ASSERT(hasFastInstanceSize(extra));
            ///__builtin_constant_p(x) : 如果x的值在編譯時能確定,那么該函數(shù)返回值為1.
       if (__builtin_constant_p(extra) && extra == 0) {
            
            return _flags & FAST_CACHE_ALLOC_MASK16;
       } else {
                /// 即得到`setFastInstanceSize`里 (word_align(newSize) + FAST_CACHE_ALLOC_DELTA16)
            size_t size = _flags & FAST_CACHE_ALLOC_MASK;
            // remove the FAST_CACHE_ALLOC_DELTA16 that was added
            // by setFastInstanceSize
            /// 減去 FAST_CACHE_ALLOC_DELTA16 得到 word_align(newSize),再進(jìn)行16字節(jié)對齊
            return align16(size + extra - FAST_CACHE_ALLOC_DELTA16);
        }
    }
    ///16字節(jié)對齊的算法
static inline size_t align16(size_t x) {
   return (x + size_t(15)) & ~size_t(15);
}

 void setFastInstanceSize(size_t newSize)
    {
        // Set during realization or construction only. No locking needed.
        uint16_t newBits = _flags & ~FAST_CACHE_ALLOC_MASK;
        uint16_t sizeBits;

        // Adding FAST_CACHE_ALLOC_DELTA16 allows for FAST_CACHE_ALLOC_MASK16
        // to yield the proper 16byte aligned allocation size with a single mask
        sizeBits = word_align(newSize) + FAST_CACHE_ALLOC_DELTA16;
        sizeBits &= FAST_CACHE_ALLOC_MASK;  
        
        if (newSize <= sizeBits) {
            newBits |= sizeBits;
        }
        //uint16_t a = ((_flags & ~FAST_CACHE_ALLOC_MASK)|(((word_align(newSize) + FAST_CACHE_ALLOC_DELTA16))&FAST_CACHE_ALLOC_MASK))&FAST_CACHE_ALLOC_MASK;  
        _flags = newBits;
    }

通過debug發(fā)現(xiàn) ,instanceSize只會走fastInstanceSize, 而這個我看了一下,在runtime入口函數(shù)里,map_images內(nèi)的realizeClassWithoutSwiftsetInstanceSize 做了cachesetFastInstanceSize

所以我們接著往下看fastInstanceSizeelse 判斷
  • size_t size = _flags & FAST_CACHE_ALLOC_MASK; 相當(dāng)于獲取到得到setFastInstanceSize(word_align(newSize) + FAST_CACHE_ALLOC_DELTA16),可以在setFastInstanceSize_flags & FAST_CACHE_ALLOC_MASK 驗證一下。
  • align16(size + extra - FAST_CACHE_ALLOC_DELTA16) 參數(shù)減去了FAST_CACHE_ALLOC_DELTA16,因為在setFastInstanceSize 加了setFastInstanceSize, 這樣得到的就是word_align(newSize)。
那么看一下word_align
#   define WORD_MASK 7UL   ( 0000 0000 0000 0111)
static inline size_t word_align(size_t x) {  
    return (x + WORD_MASK) & ~WORD_MASK;
}

~WORD_MASK:1111 1111 1111 1000
(x + WORD_MASK) & ~WORD_MASK即把(x+7) 低三位清0,結(jié)果將是8的倍數(shù),與16字節(jié)對齊算法一樣。
那么

#define FAST_CACHE_ALLOC_DELTA16      0x0008
sizeBits = word_align(newSize) + FAST_CACHE_ALLOC_DELTA16;

FAST_CACHE_ALLOC_DELTA16等于8, 因此得到的sizeBits=8的倍數(shù)+8,至少也是16。

  • 所以 size_t size = _flags & FAST_CACHE_ALLOC_MASK;獲取到的size>=16且是8的倍數(shù),再通過align16(size + extra - FAST_CACHE_ALLOC_DELTA16); 進(jìn)行16字節(jié)對齊
  • (x + size_t(15)) & ~size_t(15); 這里的xword_align(newSize)
    下面通俗的理解一下
15取反  1111 1111 1111 0000 = 1*2^15 +... 1*2^4 
與15取反 相與 前四位為0 即抹掉前4位  只要是大于15的數(shù)與它相與必定是16的倍數(shù)
x 默認(rèn) + 15  ,如果x小于16 那么+15也是大于16了,自然也是返回16
舉個例子:
x = 8
(x + size_t(15)) & ~size_t(15)
15 二進(jìn)制 :0000 0000 0000 1111
~size_t(15) : 15取反  1111 1111 1111 0000 
x + size_t(15) = 8+15 = 23  0000 0000 0001 0111
23&~15 : 0000 0000 0001 0000 =1*2^4 = 16
所以cls->instanceSize得到的size是16的倍數(shù)
接下來看看具體開辟內(nèi)存空間的方法calloc

這部分源碼需要去libmalloc里面看了,可以在mallco.c里找到實現(xiàn)

void *
calloc(size_t num_items, size_t size)
{
    return _malloc_zone_calloc(default_zone, num_items, size, MZ_POSIX);
}

MALLOC_NOINLINE
static void *
_malloc_zone_calloc(malloc_zone_t *zone, size_t num_items, size_t size,
        malloc_zone_options_t mzo)
{
    MALLOC_TRACE(TRACE_calloc | DBG_FUNC_START, (uintptr_t)zone, num_items, size, 0);

    void *ptr;
    if (malloc_check_start) {
        internal_check();
    }

    ptr = zone->calloc(zone, num_items, size);

    if (os_unlikely(malloc_logger)) {
        malloc_logger(MALLOC_LOG_TYPE_ALLOCATE | MALLOC_LOG_TYPE_HAS_ZONE | MALLOC_LOG_TYPE_CLEARED, (uintptr_t)zone,
                (uintptr_t)(num_items * size), 0, (uintptr_t)ptr, 0);
    }

    MALLOC_TRACE(TRACE_calloc | DBG_FUNC_END, (uintptr_t)zone, num_items, size, (uintptr_t)ptr);
    if (os_unlikely(ptr == NULL)) {
        malloc_set_errno_fast(mzo, ENOMEM);
    }
    return ptr;
}

到這里ptr = zone->calloc(zone, num_items, size);,單看源碼無法繼續(xù)往下了,因此編譯源碼來debug。

  • main函數(shù)calloc一下 void *p = calloc(1, 8);
  • ptr = zone->calloc(zone, num_items, size);打上斷點,lldb輸入s指令下一步,發(fā)現(xiàn)進(jìn)入default_zone_calloc
static void *
default_zone_calloc(malloc_zone_t *zone, size_t num_items, size_t size)
{
    zone = runtime_default_zone();
    
    return zone->calloc(zone, num_items, size);
}
  • 又無法跳轉(zhuǎn),還是斷住,s,發(fā)現(xiàn)進(jìn)入nano_calloc
static void *
nano_calloc(nanozone_t *nanozone, size_t num_items, size_t size)
{
    size_t total_bytes;

    if (calloc_get_size(num_items, size, 0, &total_bytes)) {
        return NULL;
    }
    printf(&total_bytes);
    if (total_bytes <= NANO_MAX_SIZE) {
        void *p = _nano_malloc_check_clear(nanozone, total_bytes, 1);
        if (p) {
            printf("1111\n");
            return p;
        } else {
            /* FALLTHROUGH to helper zone */
            
        }
    }
    malloc_zone_t *zone = (malloc_zone_t *)(nanozone->helper_zone);
    return zone->calloc(zone, 1, total_bytes);
}
  • 調(diào)試?yán)^續(xù)往下看,進(jìn)入了 void *p = _nano_malloc_check_clear(nanozone, total_bytes, 1);
    調(diào)試看到這里的total_bytes 就是傳進(jìn)來的size (通過這一步calloc_get_size(num_items, size, 0, &total_bytes)操作)
  • 而最終_nano_malloc_check_clear 里面有一個重要方法 size_t slot_bytes = segregated_size_to_fit(nanozone, size, &slot_key);
    通過調(diào)試發(fā)現(xiàn)slot_bytes 即是分配的大小
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);
    printf(&size);
    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));
}

而最終的segregated_size_to_fit則又是做了一次16字節(jié)對齊

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

可以看到當(dāng)size=0的時候,size=NANO_REGIME_QUANTA_SIZE ,如下宏,1左移4位 即 0000 0000 0001 0000 即16。

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

size!=0是進(jìn)行下面的操作

k = (size + NANO_REGIME_QUANTA_SIZE - 1) >> SHIFT_NANO_QUANTUM; 
slot_bytes = k << SHIFT_NANO_QUANTUM;  

相當(dāng)于k = (size+16-1) >> 4; slot_bytes = k <<4; 相當(dāng)于低四位清0, 和上面align16效果一樣,得到的將是16的倍數(shù)。

例子:
size=8
k = (8+16-1) >>4  即23>>4  
23: 0000 0000 0001 0111 
右移4位 :0000 0000 0000 0001
再左移4位:0000 0000 0001 0000
即低四位清0  得16
由此可見,目前版本中instanceSizecalloc都做了內(nèi)存的16字節(jié)對齊的保證。

最后實踐來看看sizeof , class_getInstanceSize , malloc_size 區(qū)別

struct NSObject_IMPL {
    Class isa;
};

struct Person_IMPL {
    struct NSObject_IMPL NSObject_IVARS; // 8字節(jié)
    int _age; // 4字節(jié)

};// 16字節(jié)
/// 結(jié)構(gòu)體內(nèi)存對齊 大小必須是最大成員大小的倍數(shù)

struct Student_IMPL {
    struct Person_IMPL Person_IVARS; // 16 字節(jié)
    int _no; // 4字節(jié)
};// 16字節(jié) 
@interface Person : NSObject
{
    @public
    int _age; // 4字節(jié)
}
@end
@implementation Person

@end
@interface Student : Person
{
    @public
    int _no;// 4字節(jié)
}
@end
@implementation Student

@end

如上Person和Student及其結(jié)構(gòu)體本質(zhì)
先各自打印出結(jié)果

        NSObject *obj = [[NSObject alloc]init];
        NSLog(@"sizeof obj: %zd",sizeof(struct NSObject_IMPL));
        NSLog(@"class_getInstanceSize obj: %zd",class_getInstanceSize([NSObject class]));
        NSLog(@"malloc_size obj: %zd",malloc_size((__bridge const void *)(obj)));
        
        Person *person = [[Person alloc]init];
        person->_age = 4;
        Student *student = [[Student alloc]init];
        student->_age = 3;
        student->_no = 5;
        NSLog(@"sizeof person: %zd",sizeof(struct Person_IMPL));
        NSLog(@"class_getInstanceSize person: %zd",class_getInstanceSize([Person class]));
        NSLog(@"malloc_size person: %zd",malloc_size((__bridge const void *)(person)));
        
        NSLog(@"sizeof student struct: %zd",sizeof(struct Student_IMPL));
        NSLog(@"sizeof student: %zd",sizeof(student));
        NSLog(@"class_getInstanceSize studetn: %zd",class_getInstanceSize([Student class]));
        NSLog(@"malloc_size studetn: %zd",malloc_size((__bridge const void *)(student)));
image.png
1. sizeof
  • 是一個運算符,傳進(jìn)來的是類型,用來計算這個類型占多大內(nèi)存,這個在 編譯器編譯階段 就會確定大小。
    比如sizeof(student),student即指針類型,占8個字節(jié),不管你是傳student或者person都是8
    上面結(jié)果有一個sizeof(struct Student_IMPL)為24 , 按理Student_IMPL結(jié)構(gòu)體內(nèi)應(yīng)該是16+4=20,這是因為結(jié)構(gòu)體內(nèi)存對齊,大小必須是最大成員大小的倍數(shù),即isa占用的8的倍數(shù)。
2. class_getInstanceSize
 size_t class_getInstanceSize(Class cls)
{
    if (!cls) return 0;
    return cls->alignedInstanceSize();
}
 // Class's ivar size rounded up to a pointer-size boundary.
 uint32_t alignedInstanceSize() const {
        return word_align(unalignedInstanceSize());
    }

看注釋是 成員變量的大小word_align 是8字節(jié)對齊的 , 所以class_getInstanceSize計算出來的大小是8字節(jié)對齊的。

3. malloc_size
  • 計算對象實際分配的內(nèi)存大小 即上面分析的 segregated_size_to_fit,是16字節(jié)對齊的。
    追蹤malloc_size的源碼 最終追蹤到
static MALLOC_INLINE size_t
__nano_vet_and_size_inner(nanozone_t *nanozone, const void *ptr, boolean_t inner)
{
    // Extracts the size of the block in bytes. Checks for a plausible ptr.
    nano_blk_addr_t p; // the compiler holds this in a register
    nano_meta_admin_t pMeta;

    p.addr = (uint64_t)ptr; // Begin the dissection of ptr

    if (NANOZONE_SIGNATURE != p.fields.nano_signature) {
        return 0;
    }

    if (nano_common_max_magazines <= p.fields.nano_mag_index) {
        return 0;
    }

    if (!inner && p.fields.nano_offset & NANO_QUANTA_MASK) { // stray low-order bits?
        return 0;
    }

    pMeta = &(nanozone->meta_data[p.fields.nano_mag_index][p.fields.nano_slot]);
    if ((void *)(pMeta->slot_bump_addr) <= ptr) {
        return 0; // Beyond what's ever been allocated!
    }
    if (!inner && ((p.fields.nano_offset % pMeta->slot_bytes) != 0)) {
        return 0; // Not an exact multiple of the block size for this slot
    }
    printf("22222");
    return pMeta->slot_bytes;
}

返回的是pMeta->slot_bytes,而這個pMeta->slot_bytes 在開辟空間時的_nano_malloc_check_clear里 可以看到蛛絲馬跡。

ptr = segregated_next_block(nanozone, pMeta, slot_bytes, mag_index);
最終走到segregated_band_grow 里面有一段賦值
pMeta->slot_bytes = (unsigned int)slot_bytes;

所以malloc_size 即是經(jīng)過segregated_size_to_fit進(jìn)行16字節(jié)對齊的slot_bytes。

如有錯誤,請大佬糾正。

end

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容