美文网首页
IOS 对象内存的开辟

IOS 对象内存的开辟

作者: Sam_1bb7 | 来源:发表于2020-09-21 23:08 被阅读0次

    IOS对象的创建中分析到了对象的创建在alloc函数,而实现alloc的主要有三个步骤

    对象创建的主要步骤
    而对象内存的开辟主要是在前两步, 基本类型的字节长度
    struct LGStruct1 {
        int c;      // 4   首先c 占用0,1,2,3
        double a;   // 8  根据内存对齐原则1,a必须要8的倍数位起始,所以不能从4开始,需要扩充都8,也就是占8~15 位
        char b;     // 1  b占用16位,满足对齐原则1
        short d;    // 2  d从17位开始的话不满足原则1,所以扩充到从18位开始,占用 18,19位,也就是占用了0~19 ,总共20个字节,但是根据原则3,总的占用字节数必须是结构体成员的最大子成员的整数倍,本结构体的最大子成员是doube,也就是a,占用8个字节,所以,结构体总的大小必须是8的整数倍,所以扩充到24.
    }struct1;  //结构体占用的内存大小是24个字节。
    
    struct LGStruct2 {
        double e;   //8   e占用0~7个字节。
        char f;      //1    f 占用第8位字节,满足内存对齐原则
        struct LGStruct1 stu; //stu从第9位字节开始,但是根据原则2,需要从内部最大子成员的整数倍开始,stu内存最大的子成员是a,占8个字节,也就是说stu要从8的整数倍开始,也就是从16位开始,stu.c占用16,17,18,19,stu.a根据原则1,要从8的倍数开始,所以在扩充到23,从24位开始,占用24~31,stu.c占用32位,满足对齐原则,stu.d 需用从2的倍数位开始,所以扩充内存到33位,从34位开始占用34,35
        char g;     //1 g从36位开始,占用36位
        short h;    //2 根据原则1,要从38位开始,占用38,39,所以结构体内部总共的元素占用是0~39,总共40个字节,根据原则3,LGStruct1和LGStruct2中内部元素中最大的子成员是double,占用8个字节,而计算的成员占用字节刚好是40,是8的倍数,所以满足原则3。
    }struct2;  //所以struct2总共占用字节数40个字节
    

    我们可以运行代码,用sizeof关键字计算一下两个结构体的大小,验证一下。

    int main(int argc, char * argv[]) {
        NSString * appDelegateClassName;
        @autoreleasepool {
            // 内存对齐
            // 对象的内存对齐 - 来自于结构体
    
            NSLog(@"%lu-%lu",sizeof(struct1),sizeof(struct2));
            
            appDelegateClassName = NSStringFromClass([AppDelegate class]);
        }
        return UIApplicationMain(argc, argv, nil, appDelegateClassName);
    }
    
    2020-09-20 23:44:48.288395+0800 001-内存对齐原则[51021:874042] 24-40
    

    为什么要进行内存对齐

    我们知道处理器然后为了提高读取效率,是以固定大小读取内存和寻址,假如对象内存不是对齐的,处理器读取的时候会出现什么问题,我们以结构体为例,来看一下

    struct LGStruct2 {
        char f;     
        double a;
    }struct1; 
    

    如上面的结构体,假设下面的地址是从0x0开始的两个内块,LGStruct2对象存储从0x00开始,如果LGStruct2没有进行内存对齐的话,我们会发现LGStruct2中的成员a的地址是0x01-0x08,占据了两个内存块,如果处理器需要读取a的值,需要读取内存块1和内存块2,然后合并内存然后才能读取出a的值,但是如果LGStruct2进行了内存对齐,a的存储地址就是0x08~0x0f,刚好占据了一个内存块,处理器只需要读取一次内存就可以读取到a的值。{所以内存对齐可以提高处理器读取效率}

    0x00 0x01 0x02 0x03 0x04 0x05 0x06 0x07    //寻址内存块1
    0x08 0x09 0x0a 0x0b 0x0c 0x0d 0x0e 0x0f     //寻址内存块2
    

    calloc 开辟对象内存

    我们在IOS对象的创建知道calloc才是真正开辟内存空间的地方,instanceSize只是计算的内存的大小,而查看calloc源码需要在libmaloc的源码中查看。

    void *
    calloc(size_t num_items, size_t size)
    {
        void *retval;
        retval = malloc_zone_calloc(default_zone, num_items, size);
        if (retval == NULL) {
            errno = ENOMEM;
        }
        return retval;
    }
    
    void *
    malloc_zone_calloc(malloc_zone_t *zone, size_t num_items, size_t size)
    {
        MALLOC_TRACE(TRACE_calloc | DBG_FUNC_START, (uintptr_t)zone, num_items, size, 0);
    
        void *ptr;
        if (malloc_check_start && (malloc_check_counter++ >= malloc_check_start)) {
            internal_check();
        }
    
        ptr = zone->calloc(zone, num_items, size);
        
        if (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);
        return ptr;
    }
    

    但是当我们的源码调试到
    ptr = zone->calloc(zone, num_items, size)的时候,发现源码无法继续了,这时候,我们可以试着打印一下zone->calloc,会发现,zone->clloc是一个函数指针,实际上指向了default_zone_calloc函数

    (lldb) po zone->calloc
    (.dylib`default_zone_calloc at malloc.c:331)
    

    找到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);
    }
    

    同样的方法继续找到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;
        }
    
        if (total_bytes <= NANO_MAX_SIZE) {
            void *p = _nano_malloc_check_clear(nanozone, total_bytes, 1);
            if (p) {
                return p;
            } else {
                /* FALLTHROUGH to helper zone */
            }
        }
        malloc_zone_t *zone = (malloc_zone_t *)(nanozone->helper_zone);
        return zone->calloc(zone, 1, total_bytes);
    }
    

    可以看到开辟内存是在_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;
    }
    

    _nano_malloc_check_clear函数里面有一个重要的方法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
        }
         //左移4位,右移4位,和之前的&~15相当,抹除了小于16的值为0
        //  abcd efgh >>4  =   0000 abcd
       // 0000 abcd <<4  =  abcd 0000
        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;
    }
    

    由此可知,clloc在开辟内存的时候对开辟的内存大小也进行了16字节的内存对齐。我们可以验证一下,验证之前我们先要了解三个函数

    • sizeof: 运算符,计算传出的数据类型所占用内存的大小,编译时完成运算。
    • class_getInstanceSize: 获取实例对象所占用的内存大小,采用8字节对齐
    • malloc_size: 获取实例对象实际开辟的内存大小。
      我们分别用三个函数计算一下对象占用的内存大小
    @interface LGPerson : NSObject
    
    @property (nonatomic, copy) NSString *name;
    @property (nonatomic, copy) NSString *nickName;
    @property (nonatomic, assign) int age;
    @property (nonatomic, assign) char height;
    
    @end
    
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            // insert code here...
            LGPerson *person = [LGPerson alloc];
            NSLog(@"%@ - %lu - %lu - %lu",person,sizeof(person),class_getInstanceSize([LGPerson class]),malloc_size((__bridge const void *)(person)));
        }
        return 0;
    }
    
    <LGPerson: 0x10041f210> - 8 - 40 - 48
    

    sizeof计算出的大小是8,因为person是一个指针,所以大小是8;
    class_getInstanceSize计算的大小是40,是因为计算出来的大小是8字节对齐的;
    malloc_size计算的是person实际开辟内存的大小,48是16的倍数,刚好和我们之前分析的alloc流程里面16字节对齐算法对应上,说明OC的对象内存开辟是以16字节对齐的。

    总结

    • OC最新的版本中对象开辟内存是以16字节对齐的,开辟内存的时候,instanceSize和calloc分别做了对内存做了两次矫正,保证了内存的16字节对齐。

    相关文章

      网友评论

          本文标题:IOS 对象内存的开辟

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