iOS 内存对齐问题

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 源网址[英文] github上有大神翻译了一篇内存对齐的英文文献,我复现了一下过程; 发现其中有个地方有出入(st...
    十曰立阅读 1,206评论 0 3
  • 作者:Eric S. Raymond转自:https://github.com/ludx/The-Lost-Art...
    FlyingReganMian阅读 1,321评论 0 4
  • Swift1> Swift和OC的区别1.1> Swift没有地址/指针的概念1.2> 泛型1.3> 类型严谨 对...
    cosWriter阅读 11,148评论 1 32
  • 作者:Eric S. Raymond 导语 我们书接上文,继续来看结构体到底是需要怎样的包装和填充。该篇是原文中占...
    金宝财商阅读 506评论 0 0
  • 最近在曹杨公园出外业的时候看见了蝉、蝉蜕、癞蛤蟆,不得不说,这些稀松平常的物种于现在的我而言也很是稀罕。多久没看到...
    魔羽文林阅读 534评论 1 0