iOS之内存对齐

关于iOS的内存对齐,首先我们思考一个问题,iOS的对象实例在内存中是如何分布的?带着这个问题我们往下看。

OC对象的内存分布

这里有一段代码,在main函数中,实例化一个person对象,通过x/4gx来打印对象实例的内存分布,如下图:


截屏2020-09-09 下午1.16.31.png

从图中的打印结果,我们看到:对象实例的内存从 0x6000001b1a40 开始,内存分布如下图:


image.png

我们看到了 person 对象在内存中的分布,但每个地址又分别代表什么呢?我们通过 po 打印一下每个地址。
(lldb) x/4gx person
0x6000001b1a40: 0x000000010bc506a0 0x0000000000000000
0x6000001b1a50: 0x000000010bc4e038 0x000000010bc4e058
(lldb) po 0x000000010bc506a0
PYPerson

(lldb) po 0x000000010bc4e038
Devin

(lldb) po 0x000000010bc4e058
码农

(lldb) 

根据打印的结果,绘成图也就是这样的:


image.png

从图中我们看到,为什么 0x6000001b1a48 指向的内容为空呢?这时我们把 age 和 h 属性的注释打开,重新运行看一下

(lldb) x/4gx person
0x6000033ade80: 0x00000001062596b0 0x0000001200000061
0x6000033ade90: 0x0000000106257038 0x0000000106257058
(lldb) po 0x12
18

(lldb) po 0x61
97

(lldb) po 0x00000001062596b0
PYPerson

(lldb) po 0x0000000106257038
Devin

(lldb) po 0x0000000106257058
码农

(lldb) 

通过打印结果,我们得出一个结论,第一个8字节的内存空间存储的是isa(地址),第二个8字节空间存储的是age和h,第三个是name,第四个是job,也就是:


image.png

看到person对象实际的内存分布后,我们再看一下PYPerson的属性声明:

@interface PYPerson : NSObject

@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *job;
@property (nonatomic, assign) int age;
@property (nonatomic, assign) char h;

@end

这时,是不是又发现另外一个问题,为什么name和job在内存中排在了age和h的后边?

结构体的内存对齐

这个问题我们先放在这里,先来看一下后边这段代码:

struct PYStudent1 {
    double height;
    int age;
    char code;
    short score;
} student1;

struct PYStudent2 {
    double height;
    char code;
    int age;
    short score;
} student2;

这里声明了两个结构体,在main函数里打印这两个结构体的大小:

NSLog(@"student1 --> %lu, student2 --> %lu", sizeof(student1), sizeof(student2));

大家觉得这两个结构体的大小应该是多少呢?是不是相等的?下面为大家提供这几种类型所占的字节数(其他类型大家可自行查阅)。


image.png

根据表中提供的各类型数据所占字节数,student1和student2是不是都是15字节呢?我们运行程序看一下打印结果:

student1 --> 16, student2 --> 24

为什么student1和student2大小不相等?
这里就引出了我们今天的主题:内存对齐

内存对齐原则:
1.数据成员对齐规则:结构(struct)(或联合(union))的数据成员,第⼀个数据成员放在offset为0的地⽅,以后每个数据成员存储的起始位置要从该成员⼤⼩或者成员的⼦成员⼤⼩(只要该成员有⼦成员,⽐如说是数组,结构体等)的整数倍开始。(⽐如int为4字节,则要从4的整数倍地址开始存储)
2.结构体作为成员:如果⼀个结构⾥有某些结构体成员,则结构体成员要从其内部最⼤元素⼤⼩的整数倍地址开始存储。(struct a⾥存有struct b,b ⾥有char、int、double等元素,那b应该从8的整数倍开始存储)
3.收尾⼯作:结构体的总⼤⼩,也就是sizeof的结果,必须是其内部最⼤成员(包括成员的成员)的整数倍,不⾜的要补⻬。

struct PYStudent1 {
    double height;  // 8字节  【0 -- 7】
    int age;        // 4字节  【8 -- 11】(8是4的倍数,所以从8开始)
    char code;      // 1字节  【11】(11是1的倍数)
    short score;    // 2字节  【12 -- 13】(12是2的倍数)
} student1;        
// 内存中占用 0-13字节,共14字节,根据内存对齐原则,总内存是8的倍数,所以是16字节

struct PYStudent2 {
    double height;  // 8字节  【0 -- 7】
    char code;      // 1字节  【8】
    int age;        // 4字节  【12 -- 15】
    short score;    // 2字节  【16 -- 17】
} student2;
// 内存中占用 0-17字节,共18字节,根据内存对齐原则,总内存是8的倍数,所以是24字节

从student1和student2的大小可以看出,虽然变量的个数和类型都相同,但是变量的顺序,会导致两个结构体的大小不同。
这也就是为什么 PYPerson 的name和job属性,声明的时候在age和h的前边,实际内存中却在age和h的后边。所以苹果其实对内存做了重排,以节省内存空间。

@interface PYPerson : NSObject

@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *job;
@property (nonatomic, assign) int age;
@property (nonatomic, assign) char h;

@end

最后一个问题:为什么要做内存对齐?引用百度百科的说法:

大部分的参考资料都是如是说的:
1、平台原因(移植原因):不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
2、性能原因:数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。

扩展:

student3占用多少内存?

struct PYStudent3 {
    double height;
    char code;
    struct PYStudent1 s;
    int age;
    short score;
} student3;
  1. height为8字节,占用第0-7位;
  2. code为1字节,因为8是1的倍数,所以占用第8位;
  3. s为16字节,根据第二条原则,PYStudent1里边最大的成员为8位字节,所以 s 开始的位置必须是8的倍数,所以从第16位开始,为16-31位;
  4. age为4位,所以是32-35位;
  5. score为2位,是占用36、37位。

5个成员共占用38位,根据第3条原则,结构体大小必须是内部最大成员的倍数,所以是8的倍数,也就是40字节。

你可能感兴趣的:(iOS之内存对齐)