OC实例对象占用内存大小问题

要想探究实例对象占用内存大小问题,首先要知道OC对象在具体的底层实现。借助clang编译器提供的指令,将OC代码转换成C++代码来剖析具体的底层实现。
以下指令,将main.m文件转换成iOS系统下64位架构(以下讨论均在64位架构基础上进行)的C++代码。

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

首先我们讨论最简单的NSObject实例对象

创建一个NSObject类型实例。

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

在转换后的main-arm64.cpp文件中,我们会发现NSObject对象的底层实现就是下面的结构体:

struct NSObject_IMPL {
    Class isa;
};

结构体中只有一个成员变量isa,类型为指针。

那么,系统会为一个NSObject实例对象分配多少内存空间呢?

我们可以引入运行时框架来获取实例对象所占用的内存空间。

#import <objc/runtime.h>

NSLog(@"size_by_class_getInstanceSize --- %zd",
              class_getInstanceSize([NSObject class]));// 8

这时我们可以看到控制台输出为8。很显然,一个isa指针占8个字节,这样看起来好像没有问题?
其实不然,通过阅读运行源码可以看出,class_getInstanceSize方法返回的是成员变量所占的内存空间,并不是系统实际为实例对象分配的空间大小。

使用malloc_size函数可以获取系统到底为NSObject实例对象分配了多少内存空间。

#import <malloc/malloc.h>

NSLog(@"size_by_malloc_size --- %zd",
              malloc_size((__bridge const void *)student));// 16

控制台打印16,这就是我们想要的答案。

为什么一个NSObject实例对象明明只需要8个字节的存储空间,系统却要为其分配16个字节的内存空间呢?这个答案在Foundtion框架的alloc方法的底层实现中。

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;
}

可以看出,系统至少会为实例对象分配16个字节的存储空间。这就解释了为什么系统会分配16个字节给NSObject实例对象。

然后我们来看看稍微复杂一点的实例对象

定义一个Person类,继承自NSObject。Person类中定义两个int类型的成员变量_age_no

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

@end

创建一个Person实例对象

Person *person = [[Person alloc] init];

在转换之后的C++代码中,可以看到NSObject和Person对象各自的底层实现。

// NSObject
struct NSObject_IMPL {
    Class isa;
};
// Person
struct Person_IMPL {
    struct NSObject_IMPL NSObject_IVARS;
    int _age;
    int _no;
};

Person的第一个成员变量是NSObject_IMPL,也就是isa指针。所以可以将Person底层实现写成下面的结构体

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

我们再来分析一下系统会为一个Person实例分配了多少内存空间?

isa指针类型占用8个字节,_age整型占4个字节,_no整型占4个字节,一共占用16个字节。

我们通过代码来验证一下。

NSLog(@"sizeby_class_getInstanceSize --- %zd",
              class_getInstanceSize([Person class]));// 16
NSLog(@"sizeby_malloc_size --- %zd",
              malloc_size((__bridge const void *)person));// 16

两次打印结果均为16,证明我们的推算没有问题。

最后我们来看更加复杂一点的实例对象

定义一个Student类,继承自Person。Student类中定义一个int类型的成员变量_weight

@interface Student : Person
{
@public
    int _weight;
}
@end
@implementation Student

@end

创建一个Student实例对象。

Student *student = [[Student alloc] init];

同样,通过转换后的C++代码可以看到NSObject、Person和Student对象各自的底层实现。

// NSObject
struct NSObject_IMPL {
    Class isa;
};

// Person
struct Person_IMPL {
    struct NSObject_IMPL NSObject_IVARS;
    int _age;
    int _no;
};

// Student
struct Student_IMPL {
    struct Person_IMPL Person_IVARS;
    int _weight;
};

推导出Student底层实现的本质就是

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

同样,我们再来推测一下系统会为一个Student实例对象分配多少内存空间?

isa指针类型占用8个字节,_age整型占4个字节,_no整型占4个字节,_weight整型占4个字节,共占用20个字节。我们可以通过代码来验证一下。

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

看到控制台打印

size_by_class_getInstanceSize --- 24
size_by_malloc_size --- 32

一脸蒙蔽!尼玛一个都没对!

这时候,就涉及到了内存对齐的问题。

首先解释class_getInstanceSize的结果为什么是24。
为结构体分配内存空间时,所分配的内存空间大小必须是结构体中占用最大内存空间成员所占用内存大小的倍数
在上面例子中,占用最大内存空间成员为isa指针,占用8个字节,所以,能容纳20个字节的数据,最小的8的倍数就是24。

再来,malloc_size的结果为什么是32。
iOS操作系统本身,在分配内存时也做了内存对齐的处理,规定分配内存大小必须是16的倍数,所以结果为32。

大家加油.png
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,189评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,577评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,857评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,703评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,705评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,620评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,995评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,656评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,898评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,639评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,720评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,395评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,982评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,953评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,195评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,907评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,472评论 2 342