美文网首页iOS面试题合集
从源码分析OC对象大小计算及内存对齐

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

作者: 哈哈西 | 来源:发表于2019-10-12 16:19 被阅读0次

    理解探究,防止忘记。给出一套示例(当前运行环境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对象结构图

    相关文章

      网友评论

        本文标题:从源码分析OC对象大小计算及内存对齐

        本文链接:https://www.haomeiwen.com/subject/wooqmctx.html