一个NSObject对象占用多少内存?

323 阅读2分钟

一个NSObject对象占用多少内存,可以通过函数打印一下

   NSObject *obj = [[NSObject alloc]init];
    NSLog(@"%zu", class_getInstanceSize([NSObject class]));//8
    printf("%zu", malloc_size((__bridge const void *)(obj)));//16

class_getInstanceSize得到的值是一个对象至少需要的内存,也即所有的成员变量总共所需的内存大小 malloc_size得到的值是系统实际分配的值,因而一个NSObject对象占用16个字节


oc的代码底层其实是c和c++的代码,通过如下命令可以将oc代码转乘c++代码

clang -rewrite-objc main.m -o main.cpp

Mac,windows,ios等不同平台,以及不同架构,生成的c++代码是不一样的,可以指定一下平台以及架构,命令如下

xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o -main-arm64.cpp

NSObject在c++中的实现其实就是结构体

@interface NSObject <NSObject> {
    Class isa Y; //8个字节 64位
}
struct NSObject_IMPL {
	Class isa;
};

结构体的大小实际上就是成员变量大小的总和,可以看到名为NSObject_IMPL的对象只有一个成员变量isa,是一个指针,按说是8个字节,为什么说实际是16个字节呢?

alloc内部调用的是allocWithZone,源码调用如下

+ (id)allocWithZone:(struct _NSZone *)zone {
    return _objc_rootAllocWithZone(self, (malloc_zone_t *)zone);
}

// objc-class-old.mm
id 
_class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone)
{
    void *bytes;
    size_t size;

    // Can't create something for nothing
    if (!cls) return nil;

    // Allocate and initialize
    size = cls->alignedInstanceSize() + extraBytes;

    // CF requires all objects be at least 16 bytes.
    if (size < 16) size = 16;

    if (zone) {
        bytes = malloc_zone_calloc((malloc_zone_t *)zone, 1, size);
    } else {
        bytes = calloc(1, size);
    }

    return objc_constructInstance(cls, bytes);
}

系统会至少分配16分字节的内存空间.


我们尝试搞一个Student类,继承自NSObject,有两个成员变量age和no,都是int类型的,搞一个HighSchoolStudent,继承自Student,有一个成员变量,grade,int类型的.

@interface Student : NSObject
   {
       int _age;
       int _no;
   }
   @end
   
   @implementation Student
   
   @end
   
   @interface HighSchoolStudent : Student
   
   @property (nonatomic,assign)int  grade;
   //{
   //    int _grade;
   //}
   @end
   
   @implementation HighSchoolStudent
   
   @end

对应的c++代码是这样

 struct NSObject_IMPL {
   	Class isa;//8
   };
   //实际占用8=8,根据内存对齐原则,为8的倍数为8
   //实际分配16,可以放下
   
   struct Student_IMPL {
   	struct NSObject_IMPL NSObject_IVARS;//8
   	int _age;//4
   	int _no;//4
   };
   //实际占用8+4+4=16,根据内存对齐原则,为8的倍数为16
   //实际分配16,可以放下
   
   struct HighSchoolStudent_IMPL {
   	struct Student_IMPL Student_IVARS;
   	int _grade;//4
   };
   //实际占用8+4+4+4=20,根据内存对齐原则,则为8的倍数为24
   //实际分配24?
   
 NSObject *obj = [[NSObject alloc]init];
           printf("obj 成员变量的实际大小 %zu\n", class_getInstanceSize([NSObject class]));
           printf("obj 分配的大小 %zu\n", malloc_size((__bridge const void *)(obj)));
           
           Student *stu = [[Student alloc]init];
           printf("stu 成员变量实际大小%zu\n",class_getInstanceSize([stu class]));
           printf("stu 分配的大小 %zu\n",malloc_size((__bridge const void *)(stu)));
           
           HighSchoolStudent *highSchoolStu = [[HighSchoolStudent alloc]init];
           printf("highSchoolStu 成员变量实际大小 %zu\n",class_getInstanceSize([HighSchoolStudent class]));
           printf("highSchoolStu 分配的大小 %zu\n",malloc_size(CFBridgingRetain(highSchoolStu)));
       }

obj 成员变量的实际大小 8

obj 分配的大小 16

stu 成员变量实际大小16

stu 分配的大小 16

highSchoolStu 成员变量实际大小 24

highSchoolStu 分配的大小 32

最后一个highSchoolStu的实际配合和我们的推测不一致,为什么呢?

接着查看alloc的调用,下面调用了calloc

void *
calloc(size_t num_items, size_t size)
{
 void *retval;
 retval = malloc_zone_calloc(default_zone, num_items, size);
 if (retval == NULL) {
 	errno = ENOMEM;
 }
 return retval;
}


void *
malloc_zone_calloc(malloc_zone_t *zone, size_t num_items, size_t size)
{
 MALLOC_TRACE(TRACE_calloc | DBG_FUNC_START, (uintptr_t)zone, num_items, size, 0);

 void *ptr;
 if (malloc_check_start && (malloc_check_counter++ >= malloc_check_start)) {
 	internal_check();
 }

 ptr = zone->calloc(zone, num_items, size);
 
 if (malloc_logger) {
 	malloc_logger(MALLOC_LOG_TYPE_ALLOCATE | MALLOC_LOG_TYPE_HAS_ZONE | MALLOC_LOG_TYPE_CLEARED, (uintptr_t)zone,
 			(uintptr_t)(num_items * size), 0, (uintptr_t)ptr, 0);
 }

 MALLOC_TRACE(TRACE_calloc | DBG_FUNC_END, (uintptr_t)zone, num_items, size, (uintptr_t)ptr);
 return ptr;
}

找到zone发现是一个结构体,zone->calloc对应如下代码

    void 	*(* MALLOC_ZONE_FN_PTR(calloc))(struct _malloc_zone_t *zone, size_t num_items, size_t size); /* same as malloc, but block returned is set to zero */

说是有一个bucks,默认分配是16的倍数,如果是24,则是32,如果是28,也是32?

未完待续

知识回顾

  1. 结构体的地址

isa在内存中的地址就是结构体的地址,因为结构体的地址就是其中第一个成员的地址

  1. 常用LLDB调试指令

    po
    p/print
    memery write 
    
  2. 内存对齐

    结构体的大小必须是最大成员的倍数

总结

OC对象的本质是结构体

一个obj实例是指向一个结构体的指针.

getInstanceSize获取的是成员变量的实际大小

malloc是系统分配的内存大小

二者的区别相当于一个盒子有多大空间和实际用了多少空间

不过获取成员变量实际大小的时候已经是内存对齐之后的大小了.