一个对象占用多大内存

235 阅读3分钟

一.首先我们来认识两个方法

class_getInstanceSize

需要导入#import <objc/runtime.h>

此方法获取的是对象实际占用的内存大小(经过内存对齐以后的)

malloc_size

需要导入#import <malloc/malloc.h>

此方法获取的是系统给分配的内存大小

二.我们利用这两个方法来打印一下一个NSObject的内存大小

NSObject *obj = [[NSObject alloc] init];

NSLog(@"%zd", class_getInstanceSize([NSObject class]));

获取obj指针所指向内存的大小

NSLog(@"%zd", malloc_size((__bridge const void *)obj));

得到的结果是8和16,为什么是这个结果呢,接下来我们通过源码来分析一下

三.源码分析

首先把我们的OC代码转换成C++代码,(通过命令行xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc oc源文件 -o 输出的文件)

在cpp文件里可以看到NSObject是一个如下的结构体

struct NSObject_IMPL {
    Class isa;
};

Class是一个指针类型,我们知道指针在64位架构里是占8个字节,那为什么系统给分配了16个字节呢,接下来我们看一下NSObject源码,系统分配内存走的是下面的方法

_objc_rootAllocWithZone(Class cls, malloc_zone_t *zone)
{
    id obj;
    (void)zone;
    obj = class_createInstance(cls, 0);
    if (slowpath(!obj)) obj = callBadAllocHandler(cls);
    return obj;
}

class_createInstance(Class cls, size_t extraBytes)
{
    return _class_createInstanceFromZone(cls, extraBytes, **nil**);
}

_class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone, 

                              bool cxxConstruct = true, 

                              size_t *outAllocatedSize = nil)

{
    ......
    size_t size = cls->instanceSize(extraBytes);
    ......
}

size_t instanceSize(size_t extraBytes) {
    size_t size = alignedInstanceSize() + extraBytes;
    // CF requires all objects be at least 16 bytes.
    if (size < 16) size = 16;
    return size;
}

uint32_t alignedInstanceSize() {
   return word_align(unalignedInstanceSize());
}

这里我们可以看到两个很重要的信息,第一个是如果对象的内存不够16个字节自动补齐为16个字节,第二个是字节对齐,在我们64位架构里是必须为8的倍数,这也就能回答刚刚的问题,接下来我们再来看一个很有趣的东西

@interface Student : NSObject
{
    int _no;

    NSString *name;

    int _height;
}
@end

四.复杂情况

我们创建一个Student类型的对象,那占用多少内存呢,系统又分配多少内存呢,首先我们来转一下C++代码看一下Student的内存结构

struct Student_IMPL {

    struct NSObject_IMPL NSObject_IVARS;

    int _no;

    NSString *name;
    
    int _height;
};

有的小伙伴可能就开始算了,8(NSObject_IVARS)+4(_no)+4(_height)+8(name)=24,占了24个字节的内存(符合内存对齐原则为8的倍数),实际分配为32字节(实际分配必须是16的倍数),那到底是不是呢,接下来我们打印一下

打印结果是32,32,那为什么是这个结果呢,大家不要忘了内存对齐,如果剩余的内存放不下的话是会开辟新的内存的,就拿这个例子举例,首先是isa指针占8个字节,_no分配8个,实际占4个,接下来是name指针需要8个字节,但是只剩余4个,不够所以会再开辟8个字节,_height占8个,最后结果是8+8+8+8=32个字节,那如果把name和_height的顺序换一下呢

@interface Student : NSObject
{
    int _no;
    
    int _height;

    NSString *name;
}
@end

此时结果会完全不一样,首先是isa指针占8个字节,_no分配8个占4个,_height需要4个字节正好还剩下4个,所以把之前剩余的空间占满,_name分配8个字节,所以系统分配了8+8+8=24个字节

相信通过这个例子,大家能够更清晰的认识到了对象的内存分配了。