美文网首页
IOS底层(六): alloc相关: 内存对齐

IOS底层(六): alloc相关: 内存对齐

作者: ShawnAlex | 来源:发表于2021-03-24 19:44 被阅读0次

    OC底层源码/原理合集

    建议先看下
    IOS底层(三): alloc相关1.初探 alloc, init, new源码分析,
    IOS底层(四): alloc相关: 对象属性在内存中的布局

    首先先介绍IOS中3种获取内存方法

    • sizeof
    • class_getInstanceSize
    • malloc_size

    sizeof

    sizeof得到的是: 数据类型占用空间的大小

    sizeof是一个操作符, 不是函数, 我们一般用sizeof计算内存大小时, 传入的主要对象是数据类型, 这个在编译器的编译阶段就会确定而不是运行时确定。

    class_getInstanceSize

    class_getInstanceSize 得到的是: 实例对象中成员变量的内存大小

    class_getInstanceSizeruntime提供的api, 用于获取类的实例对象所占用的内存空间的大小, 并返回具体的字节数。

    malloc_size

    class_getInstanceSize 得到的是: 系统实际分配的内存大小

    这个是由系统完成的, 涉及16字节内存对齐

    三种获取内存方法打印结果

    关于字节对齐, 我之前写的都是类的, 这次看下结构体的字节对齐, 例:

            struct Mystruct1 {
                char a;     // 1字节
                double b;   // 8字节
                int c;      // 4字节
                short d;    // 2字节
            } Mystruct1;
            
            struct Mystruct2 {
                double b;
                int c;
                short d;
                char a;
            } Mystruct2;
            
            NSLog(@"结构体1占用的内存大小 %lu", sizeof(Mystruct1));
            NSLog(@"结构体2占用的内存大小 %lu", sizeof(Mystruct2));
    
    结果

    为什么会是这个样子呢? 看到这个结果至少2个问题

    • 1.这个内存是怎么计算的?
    • 2.结构体1跟2的区别只是换了一个char位置, 为什么内存会变?

    内存对齐原则

    • 每个平台的编辑器都有自己的对齐系数, 程序员也可以通过预编译命令#pragma pack(n), n=1,2,4,8,16来改变这一系数, 其中n就是你指定的对齐系数。在ios中xcode默认为8, 即8字节对齐

    • 数据成员对齐可以理解为min(m, n)公式, 其中m表示当前成员开始位置, n表示当前成员所需要的位数。如果满足 m 整除 n (m % n == 0), nm位置开始存储, 反之m循环+1, 直至可以整除, 从而确定了当前成员位置。

    • 数组成员为结构体, 当结构体嵌套结构体时, "成员"的结构体的自身长度为"成员"结构体中最大成员的内存大小, 例如结构体a嵌套结构体b,b中有char、int、double等,则b的自身长度为8

    • 结构体的内存大小必须为结构体最大成员内存大小的整数倍, 不足需要补齐

    验证内存对齐原则

    先看下ios中数据类型的占用内存大小, 方便我们之后计算


    字节数对照表

    针对于上面那个例子, 先看下这个图便于理解


    结构体例子计算
    结构体1
    • a: char类型占1字节, 因为 0 % 1 = 0, 即, 0储存a
    • b: double类型占8字节,
      1 % 8 = 1, 不满足 +1,
      2 % 8 = 2, 不满足 +1
      ......
      8 % 8 = 0, 满足, 8储存b
    • c: int类型占6字节, 16 % 4 == 0, 16存储c
    • d: short类型占2字节, 20 % 2 == 0, 20存储d

    结构体1需要内存22, 由于最大字节数为8, 结构体必须是8的倍数, 所以需要向上取整, 固最终结果为24

    结构体2
    • b: double类型占8字节, 0 % 8 == 0, 0储存b
    • c: int类型占6字节, 8 % 4 == 0, 8存储c
    • d: short类型占2字节, 12 % 2 == 0, 12存储d
    • a: char类型占1字节, 因为 14 % 1 = 0, 即, 14储存a

    结构体1需要内存15, 由于最大字节数为8, 结构体必须是8的倍数, 所以需要向上取整, 固最终结果为16

    之前的问题解决了, 我们接下来看下结构体嵌套结构体

    结构体嵌套结构体
            struct Mystruct1{
                char a;     //1字节
                double b;   //8字节
                int c;      //4字节
                short d;    //2字节
            }Mystruct1;
    
            struct Mystruct2{
                double b;   //8字节
                int c;      //4字节
                short d;    //2字节
                char a;     //1字节
            }Mystruct2;
    
            struct Mystruct3 {
                double b;
                int c;
                short d;
                char a;
                struct Mystruct2 str;
            } Mystruct3;
    

    看下结构体3(结构体3为结构体2基础上嵌一个结构体2), 如果按照我们之前的定义

    • 结构体2为16, 且最大字节数8, 那么作为成员结构体2, 字节数为也为8, 结构体3最大字节数为8

    • str: struct类型占16字节, 16 % 16 == 0, 固从16起

    结构体3需要内存32, 由于最大字节数为8, 结构体必须是8的倍数, 32满足, 不需要取整, 固结果应为32, 我们验证一下

    结构体嵌结构体 结构体嵌入结构体例子计算

    我们再做一下验证一下

          struct Mystruct4{
                int a;
                struct Mystruct5{
                    double b;
                    short c;
                }Mystruct5;
            }Mystruct4;
    
    • 先看内嵌结构体5中最大为double, 8字节,
      0 % 8 = 0, 0开始放doubleb,
      8 % 2 = 0, 8放shortc, 别忘了整体需要最大值整数倍, 那么结构体5需要16字节

    • 结构体4中, 首先最大为结构体5的8字节,
      0 % 4 = 0, 0开始放inta,
      8 % 8 = 0, 8开始放 结构体5, 总共需要 24

    • 因为24是8的整数倍, 故占用内存大小为24

    验证一下


    结构体嵌结构体

    内存优化(属性重排)

    上面我们看到结构体1与结构体2里面属性是一样的, 只有排列位置不一样, 结果占用内存结果也不一样。所以结构体内存大小与结构体成员内存大小的顺序有关

    • 如果是结构体中数据成员是根据内存从小到大的顺序定义的,根据内存对齐规则来计算结构体内存大小,需要增加有较大的内存padding即内存占位符,才能满足内存对齐规则,比较浪费内存

    • 如果是结构体中数据成员是根据内存从大到小的顺序定义的,根据内存对齐规则来计算结构体内存大小,我们只需要补齐少量内存padding即可满足堆存对齐规则,这种方式就是苹果中采用的,利用空间换时间,将类中的属性进行重排,来达到优化内存的目的

    结构体的看完了, 我们看下类中的属性重排, 例如我们定义一个SATest

    属性重排例子

    main中给一些值, 然后我们读一下内存

    属性重排例子

    这个我们我们可以看到 name, age, hobby都读到可, 但是height, c1, c2并没有读取到, 而且isa旁边的内存段读出来是一串数字?

    原因是由于苹果系统对属性进行重排, 0x0000001200006261这一串就是age, c1, c2age占4个字节,c1,c2占1个字节,通过4+1+1的方式,按照8字节补齐的方式存储在同一块内存中, 我们打印一下

    属性重排例子

    这里留意一下char类型是以ASCII码形式显示, 而地址为0x0000000000000000,表示person中还有未赋值

    总结:

    • 大部分内存都是固定以内存块进行读取
    • 尽管我们在内存中采用内存对齐形式, 但是系统会自动对属性进行重排, 以此来优化内存

    相关文章

      网友评论

          本文标题:IOS底层(六): alloc相关: 内存对齐

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