美文网首页
对象size那点事(二)

对象size那点事(二)

作者: 会跑的鱼_09 | 来源:发表于2020-09-09 14:06 被阅读0次

    背景

    在上篇对象alloc那点事中遗留了一个对象带有基础类型属性后占用内存大小的问题。另外参考之前分析的对象开辟内存的调用过程如下:

    //确认创建类需要开辟的内存大小
    size = cls->instanceSize(extraBytes);
    //开辟内存
    obj = (id)calloc(1, size);
    //把isa信息填充到上面申请到的内存中
    obj->initInstanceIsa(cls, hasCxxDtor);
    

    开辟内存的calloc方法需要传入的size非常关键,这个size是通过instanceSize计算得出的,而分析 instanceSize 流程发现,它是拿对象的 实际占用大小 进行16字节对齐后得到 实际分配大小
    所以关键问题就到了如何计算 实际占用大小 !大家知道OC类经过编译后会生成相应的结构体,所以想知道OC对象的 实际占用大小 其实就是研究结构体对象在内存中分布以及对齐方式

    一、OC类示例

    前面提到对象的 实际占用大小实际分配大小,这两者有什么区别呢,先看一个例子:

    OC对象实际占用和分配大小
    • 实际占用大小40:8(isa) + 8(name) + 8(nickName) + 4(int) + 8(double) + 1(c1) + 1(c1) = 38,那为什么打印出来是40呢,那是因为结构体对象在底层需求经过字节对齐
    • 实际分配大小48:40再按16字节对齐(苹果的特性,所有对象最后都按16字节对齐),就是48。

    二、结构体字节对齐

    1.结构体对齐的原则

    结构体对象在进行字节对齐时会遵从以下三点原则:

    • 数据成员 : 结构体中第一个数据成员放在offset为0的地方,以后每个数据成员存储的起始位置要从该成员大小或者成员的子成员大小(只要该成员有子成员,比如说是数组,结构体等)的整数倍开始。
    • 结构体作为成员 : 如果一个结构里有某些结构体成员,则结构体成员要从
      其内部最大元素大小的整数倍地址开始存储(struct a里存有struct b,b
      里有char、int 、double等元素,那b应该从8的整数倍开始存储)。
    • 结构体整体对齐 : 结构体的总大小,也就是sizeof的结果,必须是其内部最大 成员的整数倍,不足的要补⻬。

    2.结构体对齐举例

    结构体字节对齐

    关键点有两个:

    • 下一个成员存储的开始位置判断,它必须是该成员自身长度的倍数。
    • 最后的收尾,必须是最大成员长度整数倍。

    3.嵌套结构体对齐举例

    嵌套结构体
    • 先计算LGStruct2中a、b、c、d起始位置和大小,共16字节。
    • str1开始位置的index为16,是其内部最大成员double的整数位,所以开始位置不变。
    • LGStruct1的大小为24(同样的计算逻辑),从index16开始24个字节存str1。
    • 总大小为16+24 = 40,再看是否最大成员的整数倍(这里要把被嵌套的结构体成员一起平铺来看)
    再来个复杂点的
    嵌套结构体

    三、再看OC类

    1.编译器优化

    不知道大家发现没有,上面结构体对齐举例中单个LGStruct1和LGStruct2它们只是int和char的位置不同,但求出来的sizeof却不一样。如果真的都24字节来开辟内存,那对系统来说当然是一种浪费,所以在系统底层会对结构体成员进行优化,减小其开辟内存的大小。

    不多解释了,注释已经很清楚

    2.看一下OC类的大小

    定义一个LGPerson类
    @interface LGPerson : NSObject
    @property (nonatomic, copy) NSString *name;
    @property (nonatomic, copy) NSString *nickName;
    @property (nonatomic, assign) double height;
    @property (nonatomic) char char1;
    @property (nonatomic, assign) int age;
    @property (nonatomic) short shot1;
    @end
    
    OC对象

    class_getInstanceSize : 获取oc类在底层对应结构体的实际占用内存大小
    malloc_size : 获取系统层面对该对象实际分配的内存大小

    class_getInstanceSize分析

    先看一下class_getInstanceSize源码

    size_t class_getInstanceSize(Class cls)
    {
        if (!cls) return 0;
        return cls->alignedInstanceSize();
    }
     uint32_t alignedInstanceSize() const {
        return word_align(unalignedInstanceSize());
    }
    static inline size_t word_align(size_t x) {
        return (x + WORD_MASK) & ~WORD_MASK;
    }
    define WORD_MASK 7UL
    

    为什么要单独把class_getInstanceSize方法拿出来讲一下呢,因为它内部进行字节对齐的代码非常经典(x + WORD_MASK) & ~WORD_MASK

    • 如果x是8的整数倍,则转换成2进制后,其低三位一定是000,加上 WORD_MASK(二进制为111),再&上WORD_MASK取反,就是把低3位置0,刚好保持了原x的值不变。
    • 如果x不是8的整数倍,则其低三3中一定有一个1,加上WORD_MASK(二进制为111)后一定会导致其第四位进位,再&上WORD_MASK取反,则只保留了8的整数倍,也确保了值比x大,所以实现了以8字节对齐的逻辑。

    最后

    虽然看起来OC对象大小的计算很简单,但在真实场景下我们的对象属性之多,结构之复杂要远超上面的例子,这里只是探究了OC对象在创建过程中的冰山一角。好了,今天就先到这吧~~

    未完待续...

    相关文章

      网友评论

          本文标题:对象size那点事(二)

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