几个示例带你去了解OC对象是怎么分配内存的

简介: 1、64位系统环境的OC对象最小分配内存为16字节。2、结构体内存对齐,是指在计算结构体大小的时候,其分配的原则:结构体大小必须是最大成员变量分配内存的倍数。3、iOS操作系统在分配内存的时候,也有内存对齐的概念,为16的倍数 :在iOS的堆空间中,如果要创建一个OC对象,分配内存时,都是16的倍数。

示例一:Student添加成员变量

新建一个 Student 对象,对象中包含两个成员,代码如下:

#import <Foundation/Foundation.h>
#import <objc/runtime.h>
#import <malloc/malloc.h>
@interface Student : NSObject
{
    @public
    int _no;
    int _age;
}
@end
@implementation Student
@end
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Student *stu = [[Student alloc] init];
        stu->_no = 4;
        stu->_age = 5;
        NSLog(@"%zd", class_getInstanceSize([Student class]));
        NSLog(@"%zd", malloc_size((__bridge const void *)stu));
    }
    return 0;
}


那么这个 Student 对象拥有多少个占用多大内存呢?执行出来,得到的答案,两个都等于16个字节。


那么为什么是等于16个字节呢?我们还是先查看一下转C\C++的代码:


// 1、Student 的结构体
struct Student_IMPL {
  struct NSObject_IMPL NSObject_IVARS;// NSObject 的结构体
  int _no;// 新成员1
  int _age;// 新对象2
};
// 3、已知 NSObject 的结构体
struct NSObject_IMPL {
    Class isa;
};
// 3、以上两个结构体,可以合成:
struct Student_IMPL {
    Class isa;// 根据上一节,我们知道 isa 占用8个字节
    int _no;// int 类型 占用4个字节
    int _age;
};


通过以上可知,Student 对象占用内存为:8 + 4 + 4 = 16个字节,刚好等于最小分配内存,所以 stu 实例对象占用内存值为16字节。


示例二:Student、Person 继承

#import <Foundation/Foundation.h>
#import <objc/runtime.h>
#import <malloc/malloc.h>
@interface Person : NSObject {
    int _age;
}
@end
@implementation Person
@end
@interface Student : Person
{
    int _no;
}
@end
@implementation Student
@end
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Student *stu = [[Student alloc] init];
        NSLog(@"stu - %zd", class_getInstanceSize([Student class]));
        NSLog(@"stu - %zd", malloc_size((__bridge const void *)stu));
        Person *person = [[Person alloc] init];
        NSLog(@"person - %zd", class_getInstanceSize([Person class]));
        NSLog(@"person - %zd", malloc_size((__bridge const void *)person));
    }
    return 0;
}


错误分析:


struct Person_IMPL {
    struct NSObject_IMPL NSObject_IVARS; // 8
    int _age; // 4
}; // 8 + 4 = 12
struct Student_IMPL {
    struct Person_IMPL Person_IVARS; // 12
    int _no; // 4
}; // 12 + 4 = 16


但实际运行结果如下:


22-05-01 15:36:03.839551+0800 Interview01[3978:66568] stu - 16
2022-05-01 15:36:03.839981+0800 Interview01[3978:66568] stu - 16
2022-05-01 15:36:03.840062+0800 Interview01[3978:66568] person - 16
2022-05-01 15:36:03.840105+0800 Interview01[3978:66568] person - 16

为什么会这样呢?这里就要讲到原则:

1、内存对齐原则:结构体的最终大小必须是最大成员大小的倍数

2、OC(64位)对象最小分配内存为16字节。


正确分析:


struct Person_IMPL {
    struct NSObject_IMPL NSObject_IVARS; // 8
    int _age; // 4
}; // 16 内存对齐:结构体的大小必须是最大成员大小的倍数
struct Student_IMPL {
    struct Person_IMPL Person_IVARS; // 16
    int _no; // 4
}; // 16:因为 Person_IVARS 结构体还有4个字节还没有引用,刚好添加上去


示例三:Person 添加属性

#import <Foundation/Foundation.h>
#import <objc/runtime.h>
#import <malloc/malloc.h>
@interface Person : NSObject
{
    @public
    int _age;
}
@property (nonatomic, assign) int height;
@end
@implementation Person
@end
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Person *person = [[Person alloc] init];
        [person setHeight:10];
        [person height];
        person->_age = 20;
        Person *person1 = [[Person alloc] init];
        person1->_age = 10;
        NSLog(@"person - %zd", class_getInstanceSize([Person class]));
        NSLog(@"person - %zd", malloc_size((__bridge const void *)person));
    }
    return 0;
}


分析:


struct Person_IMPL {
  struct NSObject_IMPL NSObject_IVARS;//8
  int _age;//4
  int _height;// 4
};// 8 + 4 + 4 = 16


添加属性会自动执行 set和 get 方法,执行 set 的方法的时候,会自动成一个 _height 成员变量。


示例四、结构体变量占用大于16

@interface MJPerson : NSObject {
    int _age;
    int _height;
    int _no;
}
@end
@implementation MJPerson
@end
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // 实例对象的所有成员变量的所占用的大小   24
        NSLog(@"class_getInstanceSize:%zd",
              class_getInstanceSize([MJPerson class]));
        // 实例对象实际分配的大小  32
        MJPerson *p = [[MJPerson alloc] init];
        NSLog(@"malloc_size:%zd",
              malloc_size((__bridge const void *)(p)));
    }
    return 0;
}


转换:


struct NSObject_IMPL {
    Class isa;
};
struct MJPerson_IMPL {
    struct NSObject_IMPL NSObject_IVARS;
    int _age;
    int _height;
    int _no;
}; // 计算结构体大小,内存对齐,24

结构体所占内存大小为24,那么实际 MJPerson *p 对象却是 32呢?

通过查看官网原码可知,苹果系统内存分配规则:


#define NANO_MAX_SIZE   256 /* Buckets sized {16, 32, 48, 64, 80, 96, 112, ...} */

根据往大了取的原则, MJPerson *p 对象实际分配了32个字节。


总结:

1、结构体内存对齐,是指在计算结构体大小的时候,遵循其分配的原则:结构体大小必须是最大成员变量分配内存的倍数。

2、iOS操作系统在分配内存的时候,也有内存对齐的概念,即为16的倍数 :在iOS的堆空间中,如果要创建一个OC对象,分配内存时,都是16的倍数。


拓展:

1、结构体最前面的成员的地址值就是就是这个结构体的地址值。

2、方法不放在实例对象中,而是放在类对象中。

3、创建一个实例对象,至少需要多少内存?


#import <objc/runtime.h>
class_getInstanceSize([NSObject class]);

4、创建一个实例对象,实际上分配了多少内存?


#import <malloc/malloc.h>
malloc_size((__bridge const void *)obj);

4、函数区别


#import <malloc/malloc.h>
// 获取一个类型有多大
size_of(int);
// 获取这个类有多大
class_getInstanceSize([NSObject class]);
// 分析:
// size_of:本质是一个运算符(编译中执行),
// class_getInstanceSize:本质是一个函数(运行中执行)。


5、LLDB常用指令


5.1、print、p:打印

5.2、po:打印对象

5.3、读取内存:


// 格式:
memory read/数量格式字节数  内存地址
// 简写
x/数量格式字节数  内存地址
//实例1:
x/3xw  0x10010
//实例2:
x  0x10010


说明:


1、数量:打印的数量

2、格式:x是16进制,f是浮点,d是10进制

3、字节数:

b:byte 1字节,h:half word 2字节

w:word 4字节,g:giant word 8字节


5.4、修改内存中的值:

memory write 内存地址 数值

memory write 0x0000010 10


Demo

InterView-NSObject的内存分析


相关文章
|
23天前
|
缓存 监控 算法
Python内存管理:掌握对象的生命周期与垃圾回收机制####
本文深入探讨了Python中的内存管理机制,特别是对象的生命周期和垃圾回收过程。通过理解引用计数、标记-清除及分代收集等核心概念,帮助开发者优化程序性能,避免内存泄漏。 ####
34 3
|
1月前
|
存储 C语言
C语言如何使用结构体和指针来操作动态分配的内存
在C语言中,通过定义结构体并使用指向该结构体的指针,可以对动态分配的内存进行操作。首先利用 `malloc` 或 `calloc` 分配内存,然后通过指针访问和修改结构体成员,最后用 `free` 释放内存,实现资源的有效管理。
122 13
|
1月前
|
存储 编译器 Linux
【c++】类和对象(上)(类的定义格式、访问限定符、类域、类的实例化、对象的内存大小、this指针)
本文介绍了C++中的类和对象,包括类的概念、定义格式、访问限定符、类域、对象的创建及内存大小、以及this指针。通过示例代码详细解释了类的定义、成员函数和成员变量的作用,以及如何使用访问限定符控制成员的访问权限。此外,还讨论了对象的内存分配规则和this指针的使用场景,帮助读者深入理解面向对象编程的核心概念。
111 4
|
2月前
|
缓存 算法 Java
JVM知识体系学习六:JVM垃圾是什么、GC常用垃圾清除算法、堆内存逻辑分区、栈上分配、对象何时进入老年代、有关老年代新生代的两个问题、常见的垃圾回收器、CMS
这篇文章详细介绍了Java虚拟机(JVM)中的垃圾回收机制,包括垃圾的定义、垃圾回收算法、堆内存的逻辑分区、对象的内存分配和回收过程,以及不同垃圾回收器的工作原理和参数设置。
89 4
JVM知识体系学习六:JVM垃圾是什么、GC常用垃圾清除算法、堆内存逻辑分区、栈上分配、对象何时进入老年代、有关老年代新生代的两个问题、常见的垃圾回收器、CMS
|
2月前
|
Java 测试技术 Android开发
让星星⭐月亮告诉你,强软弱虚引用类型对象在内存足够和内存不足的情况下,面对System.gc()时,被回收情况如何?
本文介绍了Java中四种引用类型(强引用、软引用、弱引用、虚引用)的特点及行为,并通过示例代码展示了在内存充足和不足情况下这些引用类型的不同表现。文中提供了详细的测试方法和步骤,帮助理解不同引用类型在垃圾回收机制中的作用。测试环境为Eclipse + JDK1.8,需配置JVM运行参数以限制内存使用。
38 2
|
2月前
|
存储 Java
JVM知识体系学习四:排序规范(happens-before原则)、对象创建过程、对象的内存中存储布局、对象的大小、对象头内容、对象如何定位、对象如何分配
这篇文章详细地介绍了Java对象的创建过程、内存布局、对象头的MarkWord、对象的定位方式以及对象的分配策略,并深入探讨了happens-before原则以确保多线程环境下的正确同步。
62 0
JVM知识体系学习四:排序规范(happens-before原则)、对象创建过程、对象的内存中存储布局、对象的大小、对象头内容、对象如何定位、对象如何分配
|
2月前
|
存储 Java
深入理解java对象的内存布局
这篇文章深入探讨了Java对象在HotSpot虚拟机中的内存布局,包括对象头、实例数据和对齐填充三个部分,以及对象头中包含的运行时数据和类型指针等详细信息。
30 0
深入理解java对象的内存布局
|
2月前
|
存储 编译器 C++
【C++】掌握C++类的六个默认成员函数:实现高效内存管理与对象操作(二)
【C++】掌握C++类的六个默认成员函数:实现高效内存管理与对象操作
|
2月前
|
算法 Java
JVM进阶调优系列(3)堆内存的对象什么时候被回收?
堆对象的生命周期是咋样的?什么时候被回收,回收前又如何流转?具体又是被如何回收?今天重点讲对象GC,看完这篇就全都明白了。
|
2月前
|
存储 编译器 C++
【C++】掌握C++类的六个默认成员函数:实现高效内存管理与对象操作(三)
【C++】掌握C++类的六个默认成员函数:实现高效内存管理与对象操作

热门文章

最新文章