美文网首页
iOS Struct嵌套类型的内存分析

iOS Struct嵌套类型的内存分析

作者: 携YOU手同行 | 来源:发表于2020-09-08 18:35 被阅读0次

    一,为什么要内存对齐

    在iOS开发过程中,甚至任何一门开发语言,对于内存的资源都是极其宝贵的,不能随意的浪费,所以才会存在栈内存和堆内存的情况,栈内存就是连续的空间,由系统统一分配,而堆内存是离散的,是由程序员手动开辟使用,使用完成在进行系统回收这样的一个过程。

    所以在系统为我们分配内存,然后再去读内存中相应数据的时候,如果随意存储,随意的读取相应存储的东西,这样系统的性能会大大降低,速度也是相当缓慢,从而是计算机的CPU相应的消耗过大,一直在查找、读取内存中的东西,基于这样的优化实现,所以才会有内存对齐的机制,从而使系统读取数据得到最大优化的实现。

    两种内存对齐方式

    对于iOS开发过程中存在两种系统内存对齐的方式

    • kind1: 16字节对齐
    • kind2: 8字节对齐

    eg1: 16字节对齐方式

    从objc 源码可以看出,我们系统的alloc 流程是使用的16字节对齐的方式,代码片段如下

     size_t instanceSize(size_t extraBytes) const {
            if (fastpath(cache.hasFastInstanceSize(extraBytes))) {
                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;
        }
    

    step into

    size_t fastInstanceSize(size_t extra) const
        {
            ASSERT(hasFastInstanceSize(extra));
    
            if (__builtin_constant_p(extra) && extra == 0) {
                return _flags & FAST_CACHE_ALLOC_MASK16;
            } else {
                size_t size = _flags & FAST_CACHE_ALLOC_MASK;
                // remove the FAST_CACHE_ALLOC_DELTA16 that was added
                // by setFastInstanceSize
                return align16(size + extra - FAST_CACHE_ALLOC_DELTA16);
            }
        }
    

    最后到 最核心的算法

    static inline size_t align16(size_t x) {
        return (x + size_t(15)) & ~size_t(15);
    }
    

    此处的原理我上一篇文章已经介绍过了,里边有很详细得位与计算,此处就不再赘述了。

    eg2:

    在libmalloc 开源的库里也能查到,

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

    step into

    static inline malloc_zone_t *
    inline_malloc_default_zone(void)
    {
        _malloc_initialize_once();
        // malloc_report(ASL_LEVEL_INFO, "In inline_malloc_default_zone with %d %d\n", malloc_num_zones, malloc_has_debug_zone);
        return malloc_zones[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]);
    

    最后到 也是核心算法

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

    这也是16对齐的一种证明,

    eg 8字节对齐

    在runtime源码中的 class_getInstanceSize 方法中

        // Class's ivar size rounded up to a pointer-size boundary.
        uint32_t alignedInstanceSize() const {
            return word_align(unalignedInstanceSize());
        }
    

    step into


    图片.png

    会进入标注的方法,而我们看到此算法和16字节对齐的算法一样,只是16字节的mask 是15 而8字节的WORD_MASK 是7,所以足以证明是8自己对齐,这就是对象创建时候使用的内存对齐机制。好了,字节对齐应该算介绍明白了,接下来就进入今天的主题,结构体内存对齐,

    二,结构体内存对齐

    • 简单的结构体内存分析

    我们定义两个结构体Student 和Teacher: 两个结构体的成员都一样,只是顺序不一样,我们打印相关的内存空间发现结果是不一样的,打印结果如下

    struct Student{
        long a;
        int b;
        short c;
        char d;
    }struct1;
    
    struct Tearcher{
        long a;
        short b;
        int c;
        char d;
    
    }struct2;
    
    图片.png

    为什么是这样呢,原来这就是iOS系统中内存对齐的作用导致的,接下来我们就详细的分析结果;

    在iOS系统中,所有数据类型的内存占用情况是如下:截图来自逻辑教育那个最帅的男人

    图片.png

    再根据内存对齐的规则

    1:数据成员对⻬规则:结构(struct)(或联合(union))的数据成员,第
    ⼀个数据成员放在offset为0的地⽅,以后每个数据成员存储的起始位置要
    从该成员⼤⼩或者成员的⼦成员⼤⼩(只要该成员有⼦成员,⽐如说是数组,
    结构体等)的整数倍开始(⽐如int为4字节,则要从4的整数倍地址开始存
    储。 min(当前开始的位置m n) m = 9 n = 4
    9 10 11 12

    2:结构体作为成员:如果⼀个结构⾥有某些结构体成员,则结构体成员要从
    其内部最⼤元素⼤⼩的整数倍地址开始存储.(struct a⾥存有struct b,b
    ⾥有char,int ,double等元素,那b应该从8的整数倍开始存储.)

    3:收尾⼯作:结构体的总⼤⼩,也就是sizeof的结果,.必须是其内部最⼤
    成员的整数倍.不⾜的要补⻬。

    我们从以上得知,long = 8、 int = 4、short = 2、char = 1。

    struct1 的结果分析

    • a 的存储结果 是 #0 到 #7,存储8个字节,
    • b的内存开始编号为#8 ,存储4个字节,,8 又正好是4 的整数倍 , 所以 b的存储结果是#8 到 #11,
    • c 的内存开始编号是 #12,存储2个字节,12又正好是2的整数倍,所以c的存储结果是 #12到#13,
    • d的内存开始编号是 #14,存储1个字节,同理,d的存储结果就是#14

    所以struct1 的内存实际分配为#0 到#14,15个,按照字节对齐,所以是打印结果是16;

    struct2的结果分析

    • a 的存储结果 是 #0 到 #7,存储8个字节,
    • b的内存开始编号为#8 ,存储2个字节,,8 又正好是2 的整数倍 , 所以 b的存储结果是#8 到 #9,
    • c 的内存开始编号是 #10,存储4个字节,10不是4的整数倍,所以c的存储编号移动到 #12,所以c的存储结果是 #12到#15;
    • d的内存开始编号是 #16,存储1个字节,同理,d的存储结果就是#16

    所以struct1 的内存实际分配为#0 到#16,17个,按照字节对齐,所以是打印结果是24;

    大致的草图是这样的:仅个人理解,


    图片.png
    • 嵌套结构体内存对齐

    再次,我又在项目的两个结构体中,加入了CGPoint 的一个成员,因为CGPoint本身就是一个结构体类型。

    struct Student{
        CGPoint p;
        long a;
        int b;
        short c;
        char d;
    
    }struct1;
    
    struct Tearcher{
        long a;
        short b;
        int c;
        char d;
        CGPoint p;
    
    }struct2;
    

    再次打印相关的情况如下


    图片.png

    从上图打印我们发现,CGFloat 的内存分配是 8,CGPoint 的类存是16,也就是坐标(x,y)的内存字节的和,

    原理在简单的结构体内存对齐已经介绍了,接下来说一下我发现的问题,

    问题:为什么结构体嵌套,不是作为一个整体来存储?

    从上图我们知道,如果按我们简单的结构体存储,那么struct1很好理解,但是struct 这时存储的位置是17了,按照整数倍的关系的话,内存编号应该移到32才开始存储CGPoint才对,照我想象的话最后的结果应该是47才对,然而结果打印的是40,这就是验证了一个问题,即使嵌套的结构体,系统也是把每个结构体的成员拆除出来单独存储,这样才能达到这样的结果。

    嵌套struct1分析
    • CGPoint 的 x 占据8个字节,存储是从#0到#7 ,存储8个字节;
    • CGPoint 的 y的内存编号是 #8,存储8个字节,#8又正好是8的整数倍,所以y的存储结果是#8到#15;
    • a 的存内存编号是 #16, 存储8个字节,#16正好有事8的整数倍,所以 a 的存储结果是 #16 到#23;
    • b的内存开始编号为#24 ,存储4个字节,,#24 又正好是4 的整数倍 , 所以 b的存储结果是#24 到 #17,
    • c 的内存开始编号是 #28,存储2个字节,#28又正好是2的整数倍,所以c的存储结果是 #28到#29,
    • d的内存开始编号是 #30,存储1个字节,同理,d的存储结果就是#30

    所以嵌套struct1 的内存实际分配为#0 到#30,31个,按照字节对齐,所以是打印结果是32;

    嵌套struct2分析

    • a 的存储结果 是 #0 到 #7,存储8个字节,
    • b的内存开始编号为#8 ,存储2个字节,,8 又正好是2 的整数倍 , 所以 b的存储结果是#8 到 #9,
    • c 的内存开始编号是 #10,存储4个字节,10不是4的整数倍,所以c的存储编号移动到 #12,所以c的存储结果是 #12到#15;
    • d的内存开始编号是 #16,存储1个字节,同理,d的存储结果就是#16
    • CGPoint 的 x 存储编号是#17 ,存储8个字节;#17不是8的整数倍,所以需要移动编号到#24;所以CGPoint 的 x 存储结果是#24到#31;
    • CGPoint 的 y的内存编号是 #32,存储8个字节,#32又正好是8的整数倍,所以y的存储结果是#32到#39;
      所以嵌套struct2 的内存实际分配为#0 到#39,40个,按照字节对齐,所以是打印结果是40;

    总结

    系统按照相关的内存对齐原则进行分配内存,嵌套类型不是作为一个整体来存储,而是吧嵌套子类型里边的类型逐个进行内存分配,这样既能节约内存消耗也能加快查询速度,通过自我学习,确实纠正了自己的一些错误观念。以后继续努力。

    相关文章

      网友评论

          本文标题:iOS Struct嵌套类型的内存分析

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