前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >「类与对象」如何准确获取对象的内存大小?

「类与对象」如何准确获取对象的内存大小?

作者头像
Jacklin
发布2019-12-26 19:08:53
4.5K0
发布2019-12-26 19:08:53
举报
文章被收录于专栏:攻城狮的动态

概要

本文重点讲解一下class_getInstanceSize

malloc_sizesizeOf本质和使用,以及相关源码分析。

回顾一下对象的本质

在上篇文章「类与对象」揭秘本质的第一步中,揭秘NSObject类的底层数据结构,如下所示:

代码语言:javascript
复制
struct NSObject_IMPL {
    Class isa;
};

在Xcode的Debug状态中,对比一下结构体类型和类之间的区别,具体代码如下:

代码语言:javascript
复制
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;
}

控制台打印结果如下:

可以看出objobj_imp所打印地址相同。

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

这是多么经典的一个问题啊,要回答这个问题,还需要熟悉一下这几个函数:class_getInstanceSize、malloc_size、sizeof。确切一点说,sizeof是一种操作符。

废话不多说,先撸几串,"尝尝":

代码语言:javascript
复制
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;
}

控制台打印如下:

代码语言:javascript
复制
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

咦!? 获取结果居然不一样,那是为什么呢?那就继续探究一下源码实现吧!

class_getInstanceSize

首先,这是一个runtime提供的API,用于获取类实例对象所占用的内存大小,返回所占用的字节数。

在苹果开源网站https://opensource.apple.com/release/macos-10145.html,找到对应的objc4-756.zip压缩包。

objc-class.mm类中找到该方法的具体实现:

代码语言:javascript
复制
size_t class_getInstanceSize(Class cls)
{
    if (!cls) return 0;
    return cls->alignedInstanceSize();
}

好像看不出所以然来,继续查看alignedInstanceSize实现:

代码语言:javascript
复制
// 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方法就是获取实例对象中成员变量内存大小。

仔细想一下,实例对象在创建的时候,系统应该就会分配对应的内存空间,那咱继续探究一下,在对象初始化的过程中,是否有对应的内存分配呢?

alloc

继续从Objc的源码看一下alloc函数实现,在NSObject.mm类中找到alloc以及allocFromZone方法的实现:

代码语言:javascript
复制
+ (id)alloc {
    return _objc_rootAlloc(self);
}

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

找到时机调用的核心方法是:_objc_rootAllocWithZone

代码语言:javascript
复制
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

代码语言:javascript
复制
id  class_createInstance(Class cls, size_t extraBytes)
{
    return _class_createInstanceFromZone(cls, extraBytes, nil);
}

继续查找:_class_createInstanceFromZone

代码语言:javascript
复制
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变量来源于下面的代码:

代码语言:javascript
复制
size_t size = cls->instanceSize(extraBytes);

继续看一下instanceSize函数的实现:

代码语言:javascript
复制
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找到,具体如下:

代码语言:javascript
复制
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,具体如下:

代码语言:javascript
复制
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;
}

由于该方法涉及到虚拟内存分配的流程,过于复杂,本文就再详细展开了。

理解一点即可,这个函数是获取系统实际分配的内存大小。

sizeOf

这个函数大家应该很熟悉,确切的讲,这不是一个函数,就是一个操作符,它的作用对象是数据类型,主要作用于编译时。

因此,它作用于变量时,也是对其类型进行操作。得到的结果是该数据类型占用空间大小,即size_t类型。

举个简单的例子:

代码语言:javascript
复制
struct test
{
    int a;
    char b;
};
  • 在64位架构下,sizeof(int)得到的是4个字节;
  • sizeof(test),得到的是8个字节,这里需要考虑内存对齐的问题。
  • sizeof操作符的时间复杂度是O(1)
代码语言:javascript
复制
NSLog(@"%zd", sizeof([NSObject class])); // print 8

sizeof 只会计算类型所占用的内存大小,不会关心具体的对象的内存布局;

例如:在64位架构下,自定义一个NSObject对象,无论该对象生命多少个成员变量,最后得到的内存大小都是8个字节。

应用

学习了上面获取内存大小的工具后,下面这道面试题就能很好的回答了。

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

  • 64位架构下, 系统分配了16个字节给NSObject对象(通过malloc_size函数获得);
  • NSObject对象内部只使用了8个字节的空间(可以通过class_getInstanceSize函数获得)

今天先到这。

关于更多内存分配的知识,将在下篇文章【聊一聊内存布局】中揭秘。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2019-12-19,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 猿视角 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • class_getInstanceSize
    • alloc
      • sizeOf
        • 应用
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档