美文网首页
iOS 内存对齐问题

iOS 内存对齐问题

作者: 0200a9609930 | 来源:发表于2019-11-05 20:52 被阅读0次

    1.问题

    有两个同样的 Person 类, 看一下分别在内存中占用多少空间?(64位系统, 本文按照64位系统)

    @interface Person : NSObject
    {
        char _sex;
        int _age;
        char _sex2;
    }
    @end
    
    @implementation Person
    @end
    
    @interface Person : NSObject
    {
        int _age;
        char _sex;
        char _sex2;
    }
    @end
    
    @implementation Person
    @end
    

    分别调用下面的代码

    NSLog(@"Person - %zd", class_getInstanceSize([Person class]));
    NSLog(@"Person - %zd", malloc_size((__bridge const void *)(p)));
    

    2.结果

    第一种情况:
    
    2019-11-05 20:17:59.141275+0800 02-自定义对象的本质[14104:1492184] Person - 24
    2019-11-05 20:17:59.141465+0800 02-自定义对象的本质[14104:1492184] Person - 32
    
    第一种情况:
    
    2019-11-05 20:20:08.278670+0800 02-自定义对象的本质[14139:1500726] Person - 16
    2019-11-05 20:20:08.278916+0800 02-自定义对象的本质[14139:1500726] Person - 16
    

    3.分析

    3.1 基本数据类型占用的字节数

    char a;     // 1
    short b;    // 2
    int c;      // 4
    long d;     // 8
    float e;    // 4
    double g;   // 8
    int *h;     // 8
    

    3.2 先分析第2种情况

    Class isa;     /* 8 bytes */
    int   age;     /* 4 byte */
    char  sex;     /* 1 bytes */
    char  sex2;    /* 1 bytes */
    

    可以看出, Person 对象中还有2个字节是空余的, 可以再加入1个 short 试试, 内存占用同样是16, 16, 这时已经占满了
    如果再加1个 char (前提是已经加了1个 short了), 那就要扩容了, 得到的是24, 32

    3.3 再分析第1种情况

    Class isa;     /* 8 bytes */
    char  sex;     /* 1 bytes */
    char  pad[3];  /* 3 bytes */
    int   age;     /* 4 byte */
    char  sex2;    /* 1 bytes */
    

    字符数组pad[3]意味着在这个结构体中,有3个字节的空间被浪费掉了。老派术语将其称之为“废液(slop)”。

    内存对齐的解释: 来源

    首先需要了解的是,对于现代处理器,C编译器在内存中放置基本C数据类型的方式受到约束,以令内存的访问速度更快。
    
    在x86或ARM处理器中,基本C数据类型通常并不存储于内存中的随机字节地址。
    实际情况是,除char外,所有其他类型都有“对齐要求”:
    char可起始于任意字节地址,
    2字节的short必须从偶数字节地址开始,
    4字节的int或float必须从能被4整除的地址开始,
    8比特的long和double必须从能被8整除的地址开始。
    无论signed(有符号)还是unsigned(无符号)都不受影响。
    

    如果把 sex2 删除, 那应该就得到16, 16了

    3.4 查看内存来观察

    第一种情况给成员变量添加一个 @public 以便赋值
    实例化一个 Person 对象, 给 age, sex 分别赋值

    Person *p = [[Person alloc] init];
    p->_sex = 'c';
    p->_age = 12;
    p->_sex2 = 'b';
    
    NSLog(@"Person - %zd", class_getInstanceSize([Person class]));
    NSLog(@"Person - %zd", malloc_size((__bridge const void *)(p)));
    
    1. 先打印当前实例对象的内存地址

      po p
      
    2. 然后读取内存

      memory read 内存地址
      
    3. 结果如下图


    4. 分析

      • 前面8位, 存的是 isa 指针
      • 第9位存的是 sex, 十六进制的63对应十进制的99, 也就是‘c’的 ASCII码
      • 从第10位到第12位, 总共3个字节, 是空的(00)
      • 从13位到16位, 存的是 age, 12的十六进制对应的是c
      • 下一位62对应的是 sex2, 'b'的 ASCII码是98, 再转成十六进制就是62
      • 可以使用下面的方法来修改内存的值
      memory write 内存地址 值
      

    4.该问题在 Swift 下会是什么样的?

    4.1 Class 类对象

    class Person: NSObject {
        let gender: Bool
        var age: Int = 0
        
        init(gender: Bool, age: Int) {
            self.gender = gender
            self.age = age
        }
    }
    
    var p = Person(gender: true, age: 11)
    
    let size = MemoryLayout<Person>.size
    print(size)
    
    let instanceSize = MemoryLayout<Person>.size(ofValue: p)
    print(instanceSize)
    

    得到的结果都是8, 不管属性数量的多少

    • Bool 类型占1个字节, 对齐长度也是1个字节(详细文档)
    • 当前实例对象在 Stack 区, 同时保存的内容是 指向 Heap 区
    • 根据打印出的内存地址, 再次打印 Heap 区(注意大小端, 从高位读取)
    • 大概看到了存储的值01, 0b(11)
    [图片上传中...(Xnip2019-11-06_15-25-10.jpg-4ecdc7-1573025198899-0)]

    4.1 Struct 结构体对象

    同样的代码, 只是换成了 struct
    
    struct Person {
        let gender: Bool
        var age: Int = 0
        
        init(gender: Bool, age: Int) {
            self.gender = gender
            self.age = age
        }
    }
    

    打印得出的是16
    分析:

    • Bool 类型占1个字节, 对齐长度也是1个字节
    • Int 类型占8个字节, 对齐长度是8个字节
    • 那说明 gender 后面有7个字节是浪费的, 因为 Int 对齐长度是8
    • 如果反着来, 把 age 放上面, 打印的是 9

    参考文档:
    http://www.catb.org/esr/structure-packing/#_related_reading

    相关文章

      网友评论

          本文标题:iOS 内存对齐问题

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