iOS面试题合集

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

2019-10-12  本文已影响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对象结构图

上一篇 下一篇

猜你喜欢

热点阅读