美文网首页
第二篇:iOS的内存分布探索

第二篇:iOS的内存分布探索

作者: 坚持才会看到希望 | 来源:发表于2022-04-20 22:44 被阅读0次

    1.对象的内存分布

    首先我们那看下HPWPerson这个对象,其占用了多少的内存空间,我们通过malloc_size((__bridge const void *)(p))进行打印后其显示的是48字节,那为什么是48字节呢,我们带着这个问题去探究下。

    @interface HPWPerson : NSObject
    @property (nonatomic ,copy) NSString *name;
    @property (nonatomic ,copy) NSString *hobby;
    @property (nonatomic ,assign) int age;
    @property (nonatomic ,assign) double hight;
    @property (nonatomic ,assign) short number;
    @end
    
    int main(int argc, char * argv[]) {
        NSString * appDelegateClassName;
        @autoreleasepool {
    
            appDelegateClassName = NSStringFromClass([AppDelegate class]);
           
            HPWPerson *p = [HPWPerson new];
            NSLog(@"%lu",malloc_size((__bridge const void *)(p)));
            
        }
        return UIApplicationMain(argc, argv, nil, appDelegateClassName);
    }
    
    2022-04-20 12:53:53.236808+0800 001--对象的内存分布[53714:782522] 48
    

    我们这个时候去分析下HPWPerson里的成员变量,通过下面的各个成员变量占用的空间为8+8+4+8+2=30,因为oc里其对象都有一个isa指针,这个指针占8字节,所以一起是38.但是苹果是8字节对其的最小为16字节所以HPWPerson创建的对象需要分配48字节的空间

    @property (nonatomic ,copy) NSString *name;  NSString占用8字节
    @property (nonatomic ,copy) NSString *hobby; NSString占用8字节
    @property (nonatomic ,assign) int age; int占用4字节
    @property (nonatomic ,assign) double hight; double占用8字节
    @property (nonatomic ,assign) short number; short占用2字节
    

    那我们如果在HPWPerson类里添加一个类方法+ (void)test,一个实例方法- (void)test,那分配的空间会改变吗?其实答案是不变的,这个是因为,我们的对象存储的是isa指针和成员变量的值。

    @interface HPWPerson : NSObject
    @property (nonatomic ,copy) NSString *name;
    @property (nonatomic ,copy) NSString *hobby;
    @property (nonatomic ,assign) int age;
    @property (nonatomic ,assign) double hight;
    @property (nonatomic ,assign) short number;
    - (void)test;
    + (void)test;
    @end
    
        HPWPerson *p = [HPWPerson new];
            p.name = @"鹏伟";
            p.hobby = @"girl";
            p.hight = 1.80;
            p.age = 18;
            p.number = 123;
    

    接着我们再分析下,苹果分配空间是按成员变量里的顺序,还是按我们给每个变量赋值时候的顺序来的,这个时候我们需要用到一些lldb调试,其中用到了p指令,po指令,p/x指令打印16进制数据,p/o指令打印8进制数据,p/t指令打印2进制数据,p/f指令打印浮点类型数据。x/6gx指令指的是以16进制形式去打印6个8字节的内存地址(g代表每个为8字节大小,x代表16进制形式 )

    (lldb) x/6gx p
    0x600001df4060: 0x010000010280d5c1 0x00000012007b0000
    0x600001df4070: 0x00000001028080c8 0x00000001028080e8
    0x600001df4080: 0x3ffccccccccccccd 0x0000000000000000
    

    第一个0x010000010280d5c1地址存的是8字节的isa指针,打印第3个和第5个地址如下:

    (lldb) po 0x00000001028080c8
    鹏伟
    (lldb) p/f 0x3ffccccccccccccd
    (long) $3 = 1.8
    

    通过上面我们发现,复制的时候 p.name = @"鹏伟";是放在第一位的,并且在HPWPerson的成员变量里name也是放在第一位的。但是我们通过指令打印的时候其并不是近排在isa指针后面的,而是放在第三个地址里的,这个是因为苹果其里面有自动重排属性的顺序进行优化操作,其还会把几个不满足8字节的排在一起进行优化。

    分析到这里,我们再来分析个有趣的现象:

    我们创建一个OSTestObject1类,它继承OSTestObject类,然后在OSTestObject类,也就是父类里改变int count的位置,发现其实际占用的内存空间不同。

    @interface OSTestObject : NSObject
    {
        @public
        int count;
        NSObject *objc1;
        NSObject *objc2;
    //    int count;
    }
    @end
    @implementation OSTestObject
    @end
    @interface OSTestObject1 : OSTestObject
    {
        @public
        int _count2;
    }
    @end
    
    @implementation OSTestObject1
    @end
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        
        OSTestObject1 *objc1 = [[OSTestObject1 alloc] init];
        NSLog(@"objc1实际占用的内存空间为%zd",class_getInstanceSize([OSTestObject1 class]));
        NSLog(@"系统为objc1开辟的内存空间为%zd",malloc_size((__bridge const void *)objc1));
        
    }
    
    

    当为这个顺序的时候,其实际内存空间为40字节

    @interface OSTestObject : NSObject
    {
        @public
        int count;
        NSObject *objc1;
        NSObject *objc2;
    //    int count;
    }
    
    2022-04-20 13:56:40.160391+0800 Test[54706:831054] objc1实际占用的内存空间为40
    2022-04-20 13:56:40.160449+0800 Test[54706:831054] 系统为objc1开辟的内存空间为48
    

    当为这个顺序的时候,其实际内存空间为32字节

    @interface OSTestObject : NSObject
    {
        @public
    //  int count;
        NSObject *objc1;
        NSObject *objc2;
        int count;
    }
    
    2022-04-20 13:58:22.191690+0800 Test[54755:832662] objc1实际占用的内存空间为32
    2022-04-20 13:58:22.191746+0800 Test[54755:832662] 系统为objc1开辟的内存空间为32
    

    通过上面两个输出打印发现父类里的成员变量顺序不同的时候,其实际占用的内存空间不同,这个是为什么呢?这个是因为苹果在重排的时候子类不影响父类的内存分配,如果在排到父类最后里有位置可以塞子类里成员变量的时候,就会把子类里往里面塞。当我们子类继承父类的时候,其父类是一块连续的内存空间,子类是无法修改父类的空间排列。

    2.结构体的内存分布

    这个我们来分析下结构体里暂用的内存大小,LGStruct1的大小是4字节,char是占1字节。LGStruct2占用的是1字节。

    struct LGStruct1 {
        char a;
        char b;
        char c;
        char d;
    }struct1;
    
    struct LGStruct2 {
        // 1的位置代表: 位域
        char a : 1;//有的时候不需要8字节存储,只要1个byte位就可以
        char b : 1;
        char c : 1;
        char d : 1;
    }struct2;
    
    int main(int argc, char * argv[]) {
        NSString * appDelegateClassName;
        @autoreleasepool {
            // Setup code that might create autoreleased objects goes here.
            appDelegateClassName = NSStringFromClass([AppDelegate class]);    
            NSLog(@"%lu %lu",sizeof(struct1),sizeof(struct2));
        }
        return UIApplicationMain(argc, argv, nil, appDelegateClassName);
    }
    
    
    2022-04-20 14:39:10.946945+0800  [55403:857022] 4 1
    

    如果这里设置如下:那么是占用4字节,因为不满8byte可以自行换到另外一个8byte里

    struct LGStruct2 {
        // 1的位置代表: 位域
        char a : 7;
        char b : 2;
        char c : 7;
        char d : 2;
    }struct2;
    

    3.结构体和联合体的内存分布区别

    struct LGTeacher1 {
        char *name;
        int age;
        double height;
    }t1;
    
    union LGTeacher2 {
        char *name;
        int age;
        int height;
    }t2;
    
    int main(int argc, char * argv[]) {
        NSString * appDelegateClassName;
        @autoreleasepool {
            // Setup code that might create autoreleased objects goes here.
            appDelegateClassName = NSStringFromClass([AppDelegate class]);
            t1.name = "HPW";
            t1.age = 18;
            t1.height = 2.2;
            
            t2.name = "HPW";
            t2.age = 18;
            t2.height = 2.2;        
        }
        return UIApplicationMain(argc, argv, nil, appDelegateClassName);
    }
    

    打断点调试后打印如下:

    t1  LGTeacher1  
    name    char *  "HPW"   0x0000000100b43e1f
    age int 18
    height  double  2.2000000000000002
    
    t2  LGTeacher2  
    name    char *  ""  0x0000000100000002
    age int 2
    height  int 2
    

    通过上面打印发现,结构体里打印是各个属性的值,但是联合体打印的都是乱七八糟的东西,这个是因为联合体分配的是一个空间,联合体里面的各个属性同时共用这个空间,只有在走到对应赋值语句时候打印才能显示对应的地址的值,接着继续运行这个空间就会释放了,就会看到一些乱值,这里char,int都是指针,所以联合体最大占用8字节,联合体总结是能容纳最大的成员变量,通过1计算出的大小必须是最大的成员变量(基础数据类型整数倍)。
    1)结构体里的变量可以共存,内存开辟比较粗放
    2)联合体里的变量互斥,可节省一定的内存空间

    为什么我们这里要说到联合体呢,其实我们的isa指针就是个联合体,下面我们来对isa指针进行一个探索:

    4.isa指针探索

    之前我们说了alloc一个对象后,如何和分配的内存地址进行一个关联呢,这个时候就用到了一个isa的指针,我们惊奇的发现isa指针竟然是一个联合体,这里我们再讲一个nonpointerIsa的概念,这个是苹果用来进行优化的,因为其是8字节,有64个byte位存储,像我们引用计数就存在里面。


    WechatIMG1912.jpeg
    
    union isa_t {
        isa_t() { }
        isa_t(uintptr_t value) : bits(value) { }
    
        uintptr_t bits;
    
    private:
        // Accessing the class requires custom ptrauth operations, so
        // force clients to go through setClass/getClass by making this
        // private.
        Class cls;
    
    public:
    #if defined(ISA_BITFIELD)
        struct {
            ISA_BITFIELD;  // defined in isa.h
        };
    
        bool isDeallocating() {
            return extra_rc == 0 && has_sidetable_rc == 0;
        }
        void setDeallocating() {
            extra_rc = 0;
            has_sidetable_rc = 0;
        }
    #endif
    
        void setClass(Class cls, objc_object *obj);
        Class getClass(bool authenticated);
        Class getDecodedClass(bool authenticated);
    };
    

    我们看到在isa指针里union这个联合体里有struct结构体,我们看到有ISA_BITFIELD这个,我们搜索下也就是对应isa指针的结构,在这里模拟器或者真机调试时候,区分是x86结构,还是arm64结构等,不同的结构其数据架构是不同的,nonpointer是对isa指针的优化。其中我们看到 uintptr_t shiftcls_and_sig : 52; 这个里面其实存的是类对象也就是HPWPerson

    #   if __has_feature(ptrauth_calls) || TARGET_OS_SIMULATOR
    #     define ISA_MASK        0x007ffffffffffff8ULL
    #     define ISA_MAGIC_MASK  0x0000000000000001ULL
    #     define ISA_MAGIC_VALUE 0x0000000000000001ULL
    #     define ISA_HAS_CXX_DTOR_BIT 0
    #     define ISA_BITFIELD                                                      \
            uintptr_t nonpointer        : 1;                                       \
            uintptr_t has_assoc         : 1;                                       \
            uintptr_t weakly_referenced : 1;                                       \
            uintptr_t shiftcls_and_sig  : 52;                                      \
            uintptr_t has_sidetable_rc  : 1;                                       \
            uintptr_t extra_rc          : 8 //3
    #     define RC_ONE   (1ULL<<56)
    #     define RC_HALF  (1ULL<<7)
    

    相关文章

      网友评论

          本文标题:第二篇:iOS的内存分布探索

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