概要
本文重点讲解一下class_getInstanceSize、
malloc_size和sizeOf本质和使用,以及相关源码分析。
回顾一下对象的本质
在上篇文章「类与对象」揭秘本质的第一步中,揭秘NSObject类的底层数据结构,如下所示:
struct NSObject_IMPL {
Class isa;
};
在Xcode的Debug状态中,对比一下结构体类型和类之间的区别,具体代码如下:
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSObject *obj = [[NSObject alloc] init];
struct NSObject_IMPL *obj_imp = (__bridge struct NSObject_IMPL *)(obj);
// 断点位置
NSLog(@"class_getInstanceSize = %zd", class_getInstanceSize([NSObject class]));
}
return 0;
}
控制台打印结果如下:
可以看出obj和obj_imp所打印地址相同。
一个NSObject对象到底占用多少内存呢?
这是多么经典的一个问题啊,要回答这个问题,还需要熟悉一下这几个函数:class_getInstanceSize、malloc_size、sizeof。确切一点说,sizeof是一种操作符。
废话不多说,先撸几串,"尝尝":
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSObject *obj = [[NSObject alloc] init];
NSLog(@"class_getInstanceSize = %zd", class_getInstanceSize([NSObject class]));
NSLog(@"malloc_size = %zd", malloc_size((__bridge const void *)(obj)));
NSLog(@"sizeOf = %zd", sizeof(obj));
}
return 0;
}
控制台打印如下:
2019-11-20 20:34:21 JLMacTerminalApp class_getInstanceSize = 8
2019-11-20 20:34:21 JLMacTerminalApp malloc_size = 16
2019-11-20 20:34:21 JLMacTerminalApp sizeOf = 8
咦!? 获取结果居然不一样,那是为什么呢?那就继续探究一下源码实现吧!
首先,这是一个runtime提供的API,用于获取类实例对象所占用的内存大小,返回所占用的字节数。
在苹果开源网站https://opensource.apple.com/release/macos-10145.html,找到对应的objc4-756.zip压缩包。
在objc-class.mm类中找到该方法的具体实现:
size_t class_getInstanceSize(Class cls)
{
if (!cls) return 0;
return cls->alignedInstanceSize();
}
好像看不出所以然来,继续查看alignedInstanceSize实现:
// Class's ivar size rounded up to a pointer-size boundary.
uint32_t alignedInstanceSize() {
return word_align(unalignedInstanceSize());
}
Class's ivar size rounded up to a pointer-size boundary
通过注释发现了蛛丝马迹(ivar size),翻译一下,返回实例对象中成员变量内存大小。
说白了,class_getInstanceSize方法就是获取实例对象中成员变量内存大小。
仔细想一下,实例对象在创建的时候,系统应该就会分配对应的内存空间,那咱继续探究一下,在对象初始化的过程中,是否有对应的内存分配呢?
继续从Objc的源码看一下alloc函数实现,在NSObject.mm类中找到alloc以及allocFromZone方法的实现:
+ (id)alloc {
return _objc_rootAlloc(self);
}
// Replaced by ObjectAlloc
+ (id)allocWithZone:(struct _NSZone *)zone {
return _objc_rootAllocWithZone(self, (malloc_zone_t *)zone);
}
找到时机调用的核心方法是:_objc_rootAllocWithZone
id _objc_rootAllocWithZone(Class cls, malloc_zone_t *zone)
{
id obj;
#if __OBJC2__
// allocWithZone under __OBJC2__ ignores the zone parameter
(void)zone;
obj = class_createInstance(cls, 0);
#else
if (!zone) {
obj = class_createInstance(cls, 0);
}
else {
obj = class_createInstanceFromZone(cls, 0, zone);
}
#endif
if (slowpath(!obj)) obj = callBadAllocHandler(cls);
return obj;
}
继续寻找:class_createInstance
id class_createInstance(Class cls, size_t extraBytes)
{
return _class_createInstanceFromZone(cls, extraBytes, nil);
}
继续查找:_class_createInstanceFromZone
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_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;
// Use raw pointer isa on the assumption that they might be
// doing something weird with the zone or RR.
obj->initIsa(cls);
}
if (cxxConstruct && hasCxxCtor) {
obj = _objc_constructOrFree(obj, cls);
}
return obj;
}
在调用calloc或者malloc_zone_calloc函数是需要传入size参数,可以发现size变量来源于下面的代码:
size_t size = cls->instanceSize(extraBytes);
继续看一下instanceSize函数的实现:
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;
}
CF requires all objects be at least 16 bytes.
终于看到了希望,当实例对象大小不足16个字节,系统分配给16个字节,属于系统的硬性规定。
仔细看,会发现alignedInstanceSize函数不就是class_getInstanceSize函数的内部实现。
看到这似乎明白了一些?
malloc_size
这个函数主要获取系统实际分配的内存大小,具体的底层实现也可以在源码libmalloc找到,具体如下:
size_t malloc_size(const void *ptr)
{
size_t size = 0;
if (!ptr) {
return size;
}
(void)find_registered_zone(ptr, &size);
return size;
}
核心的方法是find_registered_zone,具体如下:
static inline malloc_zone_t *
find_registered_zone(const void *ptr, size_t *returned_size)
{
// Returns a zone which contains ptr, else NULL
if (0 == malloc_num_zones) {
if (returned_size) {
*returned_size = 0;
}
return NULL;
}
// first look in the lite zone
if (lite_zone) {
malloc_zone_t *zone = lite_zone;
size_t size = zone->size(zone, ptr);
if (size) { // Claimed by this zone?
if (returned_size) {
*returned_size = size;
}
// Return the virtual default zone instead of the lite zone - see <rdar://problem/24994311>
return default_zone;
}
}
malloc_zone_t *zone = malloc_zones[0];
size_t size = zone->size(zone, ptr);
if (size) { // Claimed by this zone?
if (returned_size) {
*returned_size = size;
}
if (!has_default_zone0()) {
return zone;
} else {
return default_zone;
}
}
int32_t volatile *pFRZCounter = pFRZCounterLive; // Capture pointer to the counter of the moment
OSAtomicIncrement32Barrier(pFRZCounter); // Advance this counter -- our thread is in FRZ
unsigned index;
int32_t limit = *(int32_t volatile *)&malloc_num_zones;
malloc_zone_t **zones = &malloc_zones[1];
for (index = 1; index < limit; ++index, ++zones) {
zone = *zones;
size = zone->size(zone, ptr);
if (size) { // Claimed by this zone?
goto out;
}
}
// Unclaimed by any zone.
zone = NULL;
size = 0;
out:
if (returned_size) {
*returned_size = size;
}
OSAtomicDecrement32Barrier(pFRZCounter); // our thread is leaving FRZ
return zone;
}
由于该方法涉及到虚拟内存分配的流程,过于复杂,本文就再详细展开了。
理解一点即可,这个函数是获取系统实际分配的内存大小。
这个函数大家应该很熟悉,确切的讲,这不是一个函数,就是一个操作符,它的作用对象是数据类型,主要作用于编译时。
因此,它作用于变量时,也是对其类型进行操作。得到的结果是该数据类型占用空间大小,即size_t类型。
举个简单的例子:
struct test
{
int a;
char b;
};
NSLog(@"%zd", sizeof([NSObject class])); // print 8
sizeof 只会计算类型所占用的内存大小,不会关心具体的对象的内存布局;
例如:在64位架构下,自定义一个NSObject对象,无论该对象生命多少个成员变量,最后得到的内存大小都是8个字节。
学习了上面获取内存大小的工具后,下面这道面试题就能很好的回答了。
一个NSObject对象占用多少内存?
今天先到这。
关于更多内存分配的知识,将在下篇文章【聊一聊内存布局】中揭秘。