IOS 对象内存的开辟

2020-09-21  本文已影响0人  Sam_1bb7

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字节的内存对齐。我们可以验证一下,验证之前我们先要了解三个函数

@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字节对齐的。

总结

上一篇下一篇

猜你喜欢

热点阅读