iOS 内存对齐问题

2019-11-05  本文已影响0人  0200a9609930

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, 不管属性数量的多少

[图片上传中...(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
分析:

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

上一篇下一篇

猜你喜欢

热点阅读