iOS底层原理2-内存对齐

173 阅读7分钟

前言

上一篇文章iOS底层原理1—alloc初探我们对alloc底层进行了初探,那么这里我们再深入一些,继续使用obj4818.2源码

alloc流程深入分析

  • 创建一个类,打下断点
  • 打开汇编调用:Debug->Debug Workflow->Always Show disassembly

运行代码后我们会发现XKPerson之后是objc_alloc,这是什么?,那我们就打个符号断点,一探究竟。下面是如何打符号断点的截图:

打下符号断点后,同时我们也给alloc方法打下断点

此时关闭掉Always Show disassembly,我们马不停蹄开始运行代码,看看会发生什么。

我们会发现代码竟然走到了objc_alloc的符号断点里,继续运行,此时代码才来到alloc断点处。本该直接调用的alloc方法,怎么就先执行objc_alloc了?,这究竟是为什么? 其实这是因为LVVM进行优化,它做了些事情。未完待续……

内存对齐

今天我们的重点是内存对齐,平常我们创建一个类会有很多属性、类方法、实例方法、成员变量那么这些在内存空间中是如何存储的?占多少内存空间?又遵循了怎么的原则?接下来就一探究竟。

首先探索影响类对象内存大小的因素

  1. 创建一个类,并查看其类对象所占内存大小.
结果:内存大小为8,因为其至少有一个isa的存在,而isa的大小正好8个字节

注意:class_getInstanceSize用于获取类的实例对象所占用的内存大小,并返回具体的字节数,其本质是获取实例对象中成员变量的内存大小

  1. 给该类添加一个属性
结果:内存大小为16,可见name属性的大小是8字节
  1. 给该再类添加一个成员变量
结果:内存大小为24,可见成员变量的大小是8字节
  1. 给该再类添加一个类方法和一个实例方法
结果:内存大小为24,可见方法的大小都是是0字节
由以上四步得出以下结论:属性成员变量可以影响对象内存大小,方法不影响对象内存大小。由于属性的本质就是:成员变量+get方法+set方法,所以最终结论:成员变量是影响对象内存大小的根本因素.

其次各种数据类型所占内存大小

在上面的探索中,我们可以发现不同的数据类型,在内存中所占用的空间大小是不一样的,下图为各种类型所占的字节大小

最后内存对齐的三大基本原则

  • 1、数据成员对齐规则 结构struct(或联合(union))的数据成员,第一个数据成员放在offset为0的地方,以后每个数据成员存储的起始位置要从给成员大小或者成员的子成员大小(只要该成员有子成员,比如说数组,结构体等)的整数倍数开始(比如int为4个字节,则要从4的整数倍地址开始存储)
  • 2、结构体作为成员 如果一个结构里有某些结构体成员,则结构体成员要从其内部最大元素大小的整数倍地址开始存储,(struct a中存有struct b,b里有char,int,double等元素,那么b应该从8的整数倍开始存储),因为double占用8个字节
  • 3、结构体总大小 结构体总大小,也就是sizeof的结果,必须是其内部最大成员的整数倍,不足的要补齐

内存对齐案例

struct Struct1 {
    double a;       // 占8字节 存放在[0 7]
    char b;         // 占1字节 因为索引8是1的整数倍,所以可以存放在[8]
    int c;          // 占4字节 因为索引9、10,11,不是4的整数倍,所以空出这三个位置,存放在[12 13 14 15]
    short d;        // 占2字节 因为索引16是2的整数倍,存放在[16 17]
}struct1;           // 所以这个结构体所占总空间是[0...17],大小为18个字节,取最大元素double8字节的整倍数,所以实际存储该结构体所占内存为24个字节

struct Struct2 {
    double a;       // 占8字节 存放在[0 7]
    int b;          // 占4字节 因为索引8是4的整数倍,存放在[8 9 10 11]
    char c;         // 占1字节 因为索引12是1的整倍数,存放在[12]
    short d;        // 占2字节 因为索引13不是2的整倍数,所以空出13存放在[14 15]
}struct2;           // 所以这个结构体所占总空间是[0...15],大小为16个字节,取最大元素double8字节的整倍数,所以实际存储该结构体所占内存为24个字节

// 嵌套结构体
struct Struct3 {
    double a;           // 占8字节 存放在[0 7]
    int b;              // 占4字节 因为索引8是4的整数倍,存放在[8 9 10 11]
    char c;             // 占1字节 因为索引12是1的整倍数,存放在[12]
    short d;            // 占2字节 因为索引13不是2的整倍数,所以空出13 存放在[14 15]
    int e;              // 占4字节 因为索引16是4的整倍数,存放在[16 17 18 19]
    struct Struct1 str; // 占24字节 因为索引20不是结构体str中double8字节整数倍,所以空出20 21 22 23,存放在[24.....46]
}struct3;               // 所以这个结构体所占总空间是[0...46],大小为47个字节,取最大元素double8字节的整倍数,所以实际存储该结构体所占内存为48个字节

NSLog(@"\n第一个结构体大小:%lu\n第二个结构体大小:%lu\n第三个结构体大小:%lu", sizeof(struct1), sizeof(struct2), sizeof(struct3));

运行结果:第一个结构体大小:24   第二个结构体大小:16    第三个结构体大小:48

内存优化

#import <UIKit/UIKit.h>
#import "AppDelegate.h"
#import "XKPerson.h"
#import <objc/runtime.h>
#import <malloc/malloc.h>

int main(int argc, char * argv[]) {
    NSString * appDelegateClassName;
    @autoreleasepool {
        XKPerson *person = [XKPerson alloc];
        person.name = @"程咬金";
        person.nickName = @"一个字就是干";
        person.gender = YES;
        person.age = 30;

        NSLog(@"person——>%@", person);
        NSLog(@"sizeof——>%lu", sizeof(person));
        NSLog(@"class_getInstanceSize——>%zu", class_getInstanceSize([XKPerson class]));
        NSLog(@"malloc_size——>%zu", malloc_size((__bridge const void *)(person)));

        
        appDelegateClassName = NSStringFromClass([AppDelegate class]);
    }
    return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}

打印结果:
2021-06-09 17:39:56.653496+0800 memoryDemo[12103:551583] person——><XKPerson: 0x600003aa6970>
2021-06-09 17:39:56.653978+0800 memoryDemo[12103:551583] sizeof——>8
2021-06-09 17:39:56.654064+0800 memoryDemo[12103:551583] class_getInstanceSize——>40
2021-06-09 17:39:56.654195+0800 memoryDemo[12103:551583] malloc_size——>48
(lldb) x/6gx person
0x60000287efd0: 0x0000000100a9a770 0x000000000000001e
0x60000287efe0: 0x0000000100a95020 0x0000000100a95040
0x60000287eff0: 0x00000000000000be 0x0000000000000000
(lldb) po 0x0000000100a95020
程咬金

(lldb) po 0x0000000100a95040
一个字就是干

(lldb) po 0x00000000000000be
190

(lldb) po 0x000000000000001e
30

说明:

  • sizeof计算数据(字典、数组,变量,结构体等)内存大小, person为对象,其本质结构体指针,所以该指针的大小为8字节

  • class_getInstanceSize计算对象及成员变量占用的内存空间,需要注意父类属性和isa,8字节对齐

  • name(NSString8字节)+nickName(NSString8字节)+age(int4字节)+height(long8字节)+isa(对象指针8字节) = 36字节,按照8字节对齐规则,最终为40字节

  • malloc_size本应该向系统申请40字节大小的内存空间,但是遵循16字节对齐原则,会以16的倍数申请内存,最终向系统申请48个字节内存大小。

青山不改,绿水长流。 更多精彩底层原理,仍将继续,敬请期待……