美文网首页
iOS底层探索--Struct内存对齐

iOS底层探索--Struct内存对齐

作者: spyn_n | 来源:发表于2021-06-12 09:30 被阅读0次

    知识点须知

    C和OC各数据类型在32位和64位CPU中的占用的字节大小列表:

    C OC 32位 64位
    bool BOOL(64位) 1 1
    signed char (_ _signed char)int8_t、BOOL(32位) 1 1
    unsigned char Boolean 1 1
    short int16_t 2 2
    unsigned short unichar 2 2
    int int32_t NSInterger(32位)、boolean_t(32位) 4 4
    unsigned int boolean_t(64位)、NSUInteger(32位) 4 4
    long NSInteger(64位) 4 8
    unsigned long NSUInteger(64位) 4 8
    long long int64_t 8 8
    float CGFloat(32位) 4 4
    double CGFloat(64位) 8 8

    对象的内存大小与属性重排

    1. class_getInstanceSize
        首先我们来看一下Runtime函数class_getInstanceSize获取实例的大小。
      其底层源码是:
    size_t class_getInstanceSize(Class cls)
    {
        if (!cls) return 0;
        return cls->alignedInstanceSize();
    }
    

    类的属性大小,采用8字节对齐原则

    // Class's ivar size rounded up to a pointer-size boundary.
    uint32_t alignedInstanceSize() {
            return word_align(unalignedInstanceSize());
        }
    
    #   define WORD_MASK 7UL
    static inline uint32_t word_align(uint32_t x) {
        return (x + WORD_MASK) & ~WORD_MASK;
    }
    

    发现取决于data()->ro->instanceSize,即是对象bits在运行时的class_rw_tro,只读内存的实例大小instanceSize,其实在编译期就定了,在运行时,class_rw_t的存在(因为其实可读可写的)就决定可以动态的添加方法协议,动态关联属性。

    // May be unaligned depending on class's ivars.
        uint32_t unalignedInstanceSize() {
            assert(isRealized());
            return data()->ro->instanceSize;
        }
    

    小结:从源码可以知道,对象的大小与属性有关

    1. 验证对象大小与属性相关

      新建一个类,没有任何属性,看看创建出来的对象大小是多少

    @interface PSYPerson : NSObject
    ///**  */
    //@property (nonatomic,copy) NSString *name;
    ///**  */
    //@property (nonatomic,assign) NSInteger age;
    ///**  */
    //@property (nonatomic,copy) NSString *nickName;
    
    @end
    
    ....
    
     PSYPerson *psy1 = [[PSYPerson alloc] init];
            
     PSYPerson *psy2 = [[PSYPerson alloc] init];
            
     NSLog(@"psy1对象大小:%lu",class_getInstanceSize(psy1.class)); 
     NSLog(@"psy2对象大小:%lu",class_getInstanceSize(psy2.class));
    

    输出结果:


    从结果可以看到,为什么是8呢,明明没有任何属性?这里首先说明,对象默认继承自obj_object结构体,默认继承了一个isa指针占8个字节。
    给类添加属性@property (nonatomic,copy) NSString *name;,重新运行程序,查看打印结果:

    添加name属性后

    与我们预想的一样,为了进一步验证,添加如下属性(跑在6s真机上测试,根据上面知识点须知 C和OC不同数据类型在32位和64位CPU中占有字节大小列表):

    @interface PSYPerson : NSObject
    
    /** 8字节 */
    @property (nonatomic,copy) NSString *name;
    /** 4字节 */
    @property (nonatomic,assign) int age;
    /** 1字节  */
    @property (nonatomic) char msg;
    /** 1字节  */
    @property (nonatomic) BOOL isWorking;
    @end
    

    分析:8字节+4字节+1字节+1字节 = 14字节
    再加上isa指针8字节 :14字节+8字节 = 22字节,根据8字节对齐原则应该是24字节

    源码说只与类的ivars有关,那方法对其是否有影响呢?笔者有添加了一个类方法一个对象方法,继续输出印证,发现还是24字节;


    方法对对象大小无影响
    1. 属性重排

    对象的属性在内存中是怎样存在的呢?
    因为属性是有32字节,笔者特意打印了8段数据,实际对象的地址是从0x2807e05c0开始,占32个字节,也就是下图中红框起来的数据区,可以知道,在对象被创建出来的时候属性被默认赋值为 0 或则 nil ,其他的是脏数据。


    赋值之后,重新打印查看内存数据:
    image.png

    可发现不管属性顺序是怎样,先赋值哪一个,运行的时候,苹果会对对象的属性进行重排,使其内存优化。

    总结
    1. 对象属性内存采用8字节对齐
    2. 对象大小与ivars有关,与方法无关
    3. 苹果会进行属性重排,以优化内存

    结构体内存对齐

      上面研究了对象内存对齐,对于结构体是什么样的呢?研究结构体其实是有很大的意义,因为在底层探索过程中,基本上90%以上都是结构体共用体。

    struct PSYStruct1 {
        double a;       // 8   
        char b;         // 1    
        int c;          // 4    
        short d;        // 2 
    }struct1;
    
    struct PSYStruct2 {
        double a;       // 8 
        int b;          // 4    
        char c;         // 1   
        short d;        // 2  
    }struct2;
    

    估计很大一部分人认为,这两个结构体一抹抹一样样,大小都是16字节,可是运行结果却让人大跌眼镜:

    image.png
    结构体内存对齐
    1. 数据成员对齐规则:结构体(struct)或联合体/共用体(union)的数据成员,第一个数据成员放在offset为0️⃣的地方,以后每个数据成员存储的起始位置要从该成员大小或者成员的子成员大小(只要该成员有子成员,比如说是数组,结构体)的整数倍开始

      • eg:比如int为4字节,则要从4的整数倍地址开始存储
    2. 结构体作为成员:如果一个结构里有某些结构体成员,则结构体成员要从其内部最大元素大小的整数倍地址开始存储。

      • eg: struct a里存有struct bb里有char ,int , double 等元素,那b应该从8的整数倍开始存储)
    3. 规则三:结构体的总大小,也就是sizeof的结果,必须是其内部最大成员的整数倍,不足的要补齐。


    根据数据成员对齐规则:

    \color{#0x0000}{struct1}

    double a;   // 8 从0开始存8位 也就是 [0 , 7]
    char b;    // 1 [8]
    int c;     // 4 (9, 10, 11, [12, 13, 14, 15] 从4的整数倍开始存,也就是12开始
    short d;   // 2 [16, 17] 17个字节,但是根据规则三,大小为最大成员8的整数倍取24字节

    同理

    \color{#0x0000}{struct2}

    double a; // 8 从0开始存8位 也就是 [0 , 7]
    int b;   // 4 [8, 9, 10, 11] 因为8是4的整数倍,因此从8开始存
    char c;   // 1 [12]
    short d;   // 2 [14, 15] 根据规则三,大小为最大成员8的整数倍所以总共16个字节


    结构体嵌套情况

    struct PSYStruct3 {
        double a;
        int b;
        char c;
        short d;
        int e;
        struct PSYStruct1 str;
    }struct3;
    

    \color{#0x0000}{struct2}

    double a;   // 8字节 [0 , 7]
    int b;     // 4字节 [8, 9, 10, 11]
    char c;     // 1字节 [12]
    short d;     // 2字节 (13, [14, 15]
    int e;       // 4字节 [16, 17, 18, 19]
    struct PSYStruct1 str;// (20, 21, 22, 23, [24, 47]

    根据规则结构体作为成员:,str结构体开始存储的位置是str结构体内部最大元素的大小8的整数倍24开始,占24个字节,就是到47

    所以整个结构体占48字节。


    image.png

    相关文章

      网友评论

          本文标题:iOS底层探索--Struct内存对齐

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