从源码分析OC对象大小计算及内存对齐


理解探究,防止忘记。给出一套示例(当前运行环境64位操作系统,所以下面的计算都是基于64位操作系统的):

      Person -> NSObject -> MetaClass
      ////////////////////////////
      //Person类中无任何成员变量
      Person *person = [[Person alloc] init];
      //////////////////////////在C++的编译下得到:
      struct Person_IMPL {
         struct NSObject_IMPL NSObject_IVARS;
      }
      ////////////////////////////////////////
      NSObject *obj = [[NSObject alloc] init];
      在C++的编译下得到:
      struct NSObject_IMPL {
         Class isa;
      }
      ///////////////////////////////////////
      而Class为一个结构体的指针:
      typedef struct objc_class *Class;

所以,要想知道OC对象的大小,就只需了解指针的大小和结构体的大小。


下面只计算NSObject的大小

    NSLog(@"%zd",class_getInstanceSize([NSObject class]));
    NSObject *obj = [[NSObject alloc] init];
    NSLog(@"%zd",malloc_size((__bridge const void *)(obj)));
    ///////////////////////////输出结果为:
    8
    16

class_getInstanceSizemalloc_size产生的结果有差距

那么就需要知道这两个函数内部做了些啥了

通过查看runtime源码知道,class_getInstanceSize

    //1.
    size_t class_getInstanceSize(Class cls) {
        if (!cls) return 0;
        // 返回对齐过的实例大小
        return cls->alignedInstanceSize();
    }
    //2. Class's ivar size rounded up to a pointer-size boundary.
    uint32_t alignedInstanceSize() {
        // 返回成员变量的大小
        return word_align(unalignedInstanceSize());
    }

由源码知道,class_getInstanceSize返回的是成员变量的大小,而在[NSObject class]中只有一个指向Class类型的指针。所以class_getInstanceSize([NSObject class])获取的大小 ==等价于== 一个指向Class类型的指针的大小。

alloc的完整实现流程,可以配合着看下。下面我给出alloc==创建实例对象==的一条实现流程:

    1.
    + (id)alloc {
            return _objc_rootAlloc(self);
    }
    2.
    id _objc_rootAlloc(Class cls)  {
           return callAlloc(cls, false/*checkNil*/, true/*allocWithZone*/);
    }
    3.
    id class_createInstance(Class cls, size_t extraBytes) {
           return _class_createInstanceFromZone(cls, extraBytes, nil);
    }
    4. // 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 __OBJC2__
      if (fastpath(!cls->ISA()->hasCustomAWZ())) {
            // No alloc/allocWithZone implementation. Go straight to the allocator.
            // fixme store hasCustomAWZ in the non-meta class and 
            // add it to canAllocFast's summary
           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);
               //初始化isa
              obj->initInstanceIsa(cls, dtor);
              return obj;
          } else {
               // Has ctor or raw isa or something. Use the slower path.
               //创建一个实例createInstance
                id obj = class_createInstance(cls, 0);
                if (slowpath(!obj)) return callBadAllocHandler(cls);
               return obj;
         }
     }
    #endif
      // No shortcuts available.
      if (allocWithZone) return [cls allocWithZone:nil];
      return [cls alloc];
    }
    5.
    id  class_createInstance(Class cls, size_t extraBytes) {
       return _class_createInstanceFromZone(cls, extraBytes, nil);
    }

    6.
    _class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone, bool cxxConstruct = true, 
     size_t *outAllocatedSize = nil) {
        ...
        size_t size = cls->instanceSize(extraBytes);
        ...
    }
    7.
    size_t instanceSize(size_t extraBytes) {
        size_t size = alignedInstanceSize() + extraBytes;
        // CF requires all objects be at least 16 bytes.至少16个字节
        if (size < 16) size = 16;
        return size;
    }

alloc在创建实例对象的流程中,必然会调用instanceSize方法,所以,malloc_size结果为16字节 。

可以看出,class_getInstanceSize()函数是对象理论上需要的内存。alloc是对象实际对象需要的内存。实际分配中苹果提前有一块儿一块儿的内存块儿,这些内存块儿都是16的倍数,最大是256。所以==通过alloc实际分配内存的对象,都是16的倍数,至少16==.

注意:64位和32位操作系统,字符,基本数据,指针的大小,存在差异。


image

再新建一个Person

    //////////////////.h
    #import <Foundation/Foundation.h>
    @interface Person : NSObject {
          int a;
    }

    @end
    
    //////////////////.m
    #import "Person.h"
    @implementation Person
    
    @endNSObject_IMPL

    ///////////////在C++的编译下得到
    struct Person_IMPL {
          struct NSObject_IMPL NSObject_IVARS;
          int a;
    };
    ///////////////输出结果为
    16
    16

class_getInstanceSize([Person class])按照理论计算可能是12,然而得到大小为16。

    1.
    size_t class_getInstanceSize(Class cls)
    {
        if (!cls) return 0;
        // 返回对齐过的实例大小
        return cls->alignedInstanceSize();
    }
    2.
    uint32_t alignedInstanceSize() {
        return word_align(unalignedInstanceSize());
    }
    3.
    #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

    static inline uint32_t word_align(uint32_t x) {
      return (x + WORD_MASK) & ~WORD_MASK;
    }
    static inline size_t word_align(size_t x) {
     return (x + WORD_MASK) & ~WORD_MASK;
    }

从源码中发现实际的占用大小经过一次内存对齐操作word_align.

通过class_getInstanceSize获取的对象内存大小是,实际需要的内存大小,遵循结构体内存对齐的原则即可。详情可以了解结构体内存对齐.

结构体内存对齐规则:

  1. 第一个成员在与结构体变量偏移量为0的地址处。
  2. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
  3. 对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值。 windows(32)/VC6.0 中默认的值为8, linux(32)/GCC 中的默认值为4。
  4. 结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍。
  5. 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
    所以计算方式是:
    1.
    struct Person_IMPL {
        struct NSObject_IMPL NSObject_IVARS;//8个字节
        int a;//最后的占用大小是最大元素的倍数,所以结果为:8+4 最大元素倍数最接近的是 8 + 8
    };
    2.
    struct NSObject_IMPL {
        Class isa;//isa 指向objc_class结构体对象的指针
    }
    3.
    typedef struct objc_class *Class;
    4.
    struct objc_class : objc_object {
        ...
    }

下面,我再给出几个例子,可以分析下最后打印结果

@interface SuperPerson : Person {
    int d;
}

@interface Person : NSObject {
    char a[6];
}
///////////

NSLog(@"%zd",class_getInstanceSize([SuperPerson class]));
SuperPerson *p = [[SuperPerson alloc] init];
NSLog(@"%zd",malloc_size((__bridge const void *)(p)));
@interface SuperPerson : Person {
    char d[3];
}

@interface Person : NSObject {
    char a[6];
}
///////////

NSLog(@"%zd",class_getInstanceSize([SuperPerson class]));
SuperPerson *p = [[SuperPerson alloc] init];
NSLog(@"%zd",malloc_size((__bridge const void *)(p)));
@interface SuperPerson : Person {
    int d;
}

@interface Person : NSObject {
    int a;
    char b;
}
///////////

NSLog(@"%zd",class_getInstanceSize([SuperPerson class]));
SuperPerson *p = [[SuperPerson alloc] init];
NSLog(@"%zd",malloc_size((__bridge const void *)(p)));

理解内存对齐

        struct stu{
            long a;
            int b;
            char c;
        };

        struct tea{
            int n;
            struct stu student;
            long m;
        };

        //存a 对齐数为8 从对齐数的整数倍0开始存,存b 对齐数为4 从对齐数的整数倍8开始存,存c 对齐数为1 从对齐数的整数倍12开始存,遵循结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍,得出16
        printf("%lu\n",sizeof(struct stu));
        //存n 对齐数为4 从对齐数的整数倍0开始存,存结构体stu 对齐数为8 从对齐数的整数倍8开始存,存a 对齐数为8 从对齐数的整数倍8开始存,存b 对齐数为4 从对齐数的整数倍12开始存,存c 对齐数为1 从对齐数的整数倍16开始存,存m 对齐数为8 从对齐数的整数倍24开始存,遵循结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍,得出32
        printf("%lu\n",sizeof(struct tea));

OC对象结构图

OC对象结构图

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

推荐阅读更多精彩内容