c++ 文件是否被占用_[[NSObject alloc] init]占用多少空间?

b6cdbf771adfc89cfa359c791f717c75.png

Objective-C的本质

Objective-C代码底层实现其实都是CC++代码。

9250bfbaab5eed744ff3d882fd936b5e.png
  • Objective-C的面向对象都是基于CC++的结构体这种数据结构实现的。

将Objective-C转为C/C++

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

main.m文件所在的目录执行上面的命令,将Objective-C代码转为C++代码,输出文件为main.cpp。这个命令并没有指定编辑的平台与架构,针对iOS系统,我们可以用下面这个命令:

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

指定sdk为iphoneos,针对iOS系统,-arch arm64 表示针对arm64架构进行编译,输出文件为main-arm64.cpp

注: iOS架构:模拟器i386 32bitarmv7 64bitarm64

xcrun 是Xcode的命令,如果无法执行可能是因为装了两个版本的Xcode。在命令行指定一下Xcode路径:

sudo xcode-select --switch /Applications/Xcode.app/Contents/Developer/

NSObject底层实现

NSObject.h 文件中,我们可以看到NSObject的声明如下图左, 在main-arm64.cpp 中可以发现NSObject的实现如下图右。可以得出结论,NSObject的底层是使用了C/C++的结构体来实现的。

5fe5caec557eabe7ca9932cf583f54f0.png

接下来,我们继续查看Class的声明,如下,可以看到,Class是一个指向结构体变量的指针。因为在64位的系统中,一个指针占用8个字节,所有上图中的isa占用8个字节。

typedef struct objc_class *Class;

为了获取NSOject对象所占的内存空间,我们可以通过malloc_size 函数获取对象所占用内存空间的大小。注意,需要导入库 malloc/malloc.h

#import <malloc/malloc.h>

NSObject *obj = [[NSObject alloc] init];
//获得obj指针所指向内存的大小 输出结果为 16
NSLog(@"%zd", malloc_size((__bridge const void*)obj));

另外,我们还可以通过class_getInstanceSize方法获取对象的所有成员变量所占用的内存空间。注意,需要导入库objc/runtime.h。在上面的分析中,我们已经确认NSObject内部只有一个isa成员变量,它是一个指向Class结构体的指针变量。

#import <objc/runtime.h>

//获得NSObject实例对象的成员变量所占用的大小。 输出结果为 8
NSLog(@"%zd", class_getInstanceSize([NSObject class]));

我们也可以分析一下class_getInstanceSize的源码实现,如下:

//源码版本:objc4-723

size_t class_getInstanceSize(Class cls)
{
    if (!cls) return 0;
    return cls->alignedInstanceSize();
}

//Class's ivar size rounded up to a pointer-size boundary.
//类的成员变量的大小,word_align 对齐操作
uint32_t alignedInstanceSize() {
  return word_align(unalignedInstanceSize());
}

// May be unaligned depending on class's ivars.
// 类的成员变量的未对齐的大小
uint32_t unalignedInstanceSize() {
  return data()->ro->instanceSize;
}

//对齐函数实现
static inline size_t word_align(size_t x) {
    return (x + WORD_MASK) & ~WORD_MASK;
}

#ifdef __LP64__
#   define WORD_SHIFT 3UL
#   define WORD_MASK 7UL
#   define WORD_BITS 64
#else
#   define WORD_SHIFT 2UL
#   define WORD_MASK 3UL
#   define WORD_BITS 32
#endif

alloc 源码:

//源码版本:objc4-723

+ (id)alloc {
    return _objc_rootAlloc(self);
}

//Base class implementation of +alloc. cls is not nil.
// Calls [cls allocWithZone:nil].
id _objc_rootAlloc(Class cls)
{
    return callAlloc(cls, false/*checkNil*/, true/*allocWithZone*/);
}

// Call [cls alloc] or [cls allocWithZone:nil], with appropriate 
// shortcutting optimizations.
static ALWAYS_INLINE id callAlloc(Class cls, bool checkNil, bool allocWithZone=false)
{
    if (slowpath(checkNil && !cls)) return nil;
    if (fastpath(!cls->ISA()->hasCustomAWZ())) {
        if (fastpath(cls->canAllocFast())) {
            // No ctors, raw isa, etc. Go straight to the metal.
            bool dtor = cls->hasCxxDtor();
            id obj = (id)calloc(1, cls->bits.fastInstanceSize());
            if (slowpath(!obj)) return callBadAllocHandler(cls);
            obj->initInstanceIsa(cls, dtor);
            return obj;
        }
        else {
            // Has ctor or raw isa or something. Use the slower path.
            id obj = class_createInstance(cls, 0);
            if (slowpath(!obj)) return callBadAllocHandler(cls);
            return obj;
        }
    }
    if (allocWithZone) return [cls allocWithZone:nil];
    return [cls alloc];
}

//创建实例
id class_createInstance(Class cls, size_t extraBytes)
{
    return _class_createInstanceFromZone(cls, extraBytes, nil);
}

static __attribute__((always_inline)) id
_class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone, 
                              bool cxxConstruct = true, 
                              size_t *outAllocatedSize = nil)
{
    if (!cls) return nil;

    assert(cls->isRealized());

    // Read class's info bits all at once for performance
    bool hasCxxCtor = cls->hasCxxCtor();
    bool hasCxxDtor = cls->hasCxxDtor();
    bool fast = cls->canAllocNonpointer();

  	//获取size
    size_t size = cls->instanceSize(extraBytes);
    if (outAllocatedSize) *outAllocatedSize = size;

    id obj;
    if (!zone  &&  fast) {
        obj = (id)calloc(1, size);
        if (!obj) return nil;
        obj->initInstanceIsa(cls, hasCxxDtor);
    } 
    else {
        if (zone) {
            obj = (id)malloc_zone_calloc ((malloc_zone_t *)zone, 1, size);
        } else {
            obj = (id)calloc(1, size);
        }
        if (!obj) return nil;
        obj->initIsa(cls);
    }

    if (cxxConstruct && hasCxxCtor) {
        obj = _objc_constructOrFree(obj, cls);
    }

    return obj;
}

size_t instanceSize(size_t extraBytes) {
  //alignedInstanceSize 就是 class_getInstanceSize 也就是内部变量所占用的空间大小
  size_t size = alignedInstanceSize() + extraBytes;
  // CF requires all objects be at least 16 bytes.
  //最小为16
  if (size < 16) size = 16;
  return size;
}

我们可以画出[[NSObject alloc] init] 执行后的内存结构图。

841d9692befa0e279e2cf0e0b7dbbb40.png

至此,我们分析得到结论,NSObject对象占用16个字节的内存空间,其中前8个字节是isa变量,它是一个指向Class结构体的指针。

查看内存布局

  1. Debug —> Debug Workflow —> View Memory

de20ad66c55544c62e1c67b73b282007.png
  1. 通过LLDB查看内存布局:

通过memory read 指令可以读取内存地址。简写为x。其后面可以跟上数量/格式/字节数。

如:x/4xg 0x10312b010 表示 读写每次读取8个字节,读写4组数据,并以16进制的形式表示。

格式:x是16进制,f是浮点数,d是10进制。

字节大小:b byte是1字节, h half world 是2字节,w world是4字节, g giant world是8字节。

(lldb) po obj
<NSObject: 0x10312b010> (lldb) memory read 0x10312b010
0x10312b010: 19 91 62 98 ff ff 1d 00 00 00 00 00 00 00 00 00 ..b.............
0x10312b020: 2d 5b 4e 53 54 61 62 50 69 63 6b 65 72 56 69 65 -[NSTabPickerVie (lldb) x 0x10312b010
0x10312b010: 19 91 62 98 ff ff 1d 00 00 00 00 00 00 00 00 00 ..b.............
0x10312b020: 2d 5b 4e 53 54 61 62 50 69 63 6b 65 72 56 69 65 -[NSTabPickerVie (lldb) x/3xg 0x10312b010
0x10312b010: 0x001dffff98629119 0x0000000000000000
0x10312b020: 0x50626154534e5b2d (lldb) x/4xg 0x10312b010
0x10312b010: 0x001dffff98629119 0x0000000000000000
0x10312b020: 0x50626154534e5b2d 0x65695672656b6369

修改内存中的值

使用 memory write 命令可以修改内存中某个位置所表示的内容。下面命令是要修改0x10312b010起始第9个字节,将其内容从0改为8。

(lldb) memory read 0x10312b010
0x10312b010: 19 91 62 98 ff ff 1d 00 00 00 00 00 00 00 00 00 ..b.............
0x10312b020: 2d 5b 4e 53 54 61 62 50 69 63 6b 65 72 56 69 65 -[NSTabPickerVie (lldb) memory write 0x10312b018 8 (lldb) memory read 0x10312b010
0x10312b010: 19 91 62 98 ff ff 1d 00 08 00 00 00 00 00 00 00 ..b.............
0x10312b020: 2d 5b 4e 53 54 61 62 50 69 63 6b 65 72 56 69 65 -[NSTabPickerVie

复杂的模型

定义一个类继承自NSObject

@interface Student : NSObject {
    @public
    int _no;
    int _age;
}
@end

@implementation Student
@end

将该类编译成C/C++代码,其实现如下:

struct NSObject_IMPL {
 Class isa;
};

struct Student_IMPL {
 struct NSObject_IMPL NSObject_IVARS;
 int _no;
 int _age;
};

Student将其父类NSObject的成员变量都继承过来了,相当于:

struct Student_IMPL {
 Class isa;
 int _no;
 int _age;
};

在这个结构中,isa占用8个字节,_no_age各占用4个字节,所以,Student内部的成员变量一共占用16个字节的空间。

即:NSLog(@"%zd", class_getInstanceSize([Student class])); 输出结果为16。

NSLog(@"%zd", malloc_size((__bridge const void *)student)); 输出结果也为16,因为内部成语变量的大小为16,刚好为alloc创建出来的最小空间相等。

Student的内存结构如下:

4fbcc11386622f5d2ebc7393c0c71d50.png

我们可以构造一个结构体,将student对象强制转换为结构体,如下:我们就可以通过结构体正确的访问到内部变量。

struct Student_Impl {
    Class isa;
    int _no;
    int _age;
};

struct Student_Impl *impl = (__bridge struct Student_Impl *)student;
NSLog(@"no=%d, age=%d", impl->_no, impl->_age);

我们可以进一步证实isa的起始地址就是student指向的地址。

(lldb) po &impl->isa
<Student: 0x103905a40> (lldb) po student
<Student: 0x103905a40>

更为复杂的模型

@interface Person : NSObject {
    @public
    int _age;
}
@end

@implementation Person
@end

@interface Student : Person {
    @public
    int _no;
}
@end

@implementation Student
@end

转换为C/C++为:

struct Student_IMPL {
    struct Person_IMPL Person_IVARS;
    int _no;
};
 
struct Person_IMPL {
  struct NSObject_IMPL NSObject_IVARS;
  int _age;
};
struct NSObject_IMPL {
  Class isa;
};

内存分布:

eec4d1ade6a4a681a726267800f47131.png
Person *person = [[Person alloc] init];
//16 虽然真实大小为12,由于内存对齐,为16
NSLog(@"%zd", class_getInstanceSize([person class])); 
NSLog(@"%zd", malloc_size((__bridge const void *)person)); //16

Student *student = [[Student alloc] init];
NSLog(@"%zd", class_getInstanceSize([student class])); //16
NSLog(@"%zd", malloc_size((__bridge const void *)student)); //16

注,内存对齐:结构体的大小必须是最大成员大小的倍数。

开辟内存空间大小规则

@interface Person : NSObject {
    @public
    int _age;
    int _no;
}
@end

@implementation Person
@end

@interface Student : Person{
     @public
    int data;
}
@end

在上面这种类的结构中,我们打印出变量空间大小和内存大小。

NSLog(@"%zd", class_getInstanceSize([Student class])); //24
NSLog(@"%zd", malloc_size((__bridge const void *)student)); //32

class_getInstanceSize 这个大小是16+8(int 4, 内存对齐为8) = 24,应该不难理解。

malloc_size 这个为32 就有点难理解了,原因是操作系统在开辟内存的时候不是你需要多少就开辟多少,而是要根据一定的规则,为了方便内存管理与寻址,iOS系统开辟内存的空间都是16的倍数,所有我们需要24个字节的大小,操作系统给我们分配了32个字节。具体开辟方式可以参考资料一。

参考

参考一:https://yq.aliyun.com/articles/3065

参考二:MJ底层原理

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值