OC 对象内存探索(内存对齐)

OC底层原理学习

iOS中获取内存大小的三种方式

  1. sizeof
    得到的结果是数据类型占用的空间大小,传入的参数是数据类型,在编译阶段就会确定的大小
  2. class_getInstanceSize
    导入头文件runtime.h,runtime提供的api,获取对象的内存大小
  3. malloc_size
    导入头文件malloc.h,获取系统实际分配的内存大小

NSObject内存对齐

结果
sizeof:sizeof 打印的是 objc的指针,一个指针8字节
class_getInstanceSizeNSObject没有任何属性,但是他有一个isa指针,所以它的内存大小也是8字节
malloc_size:16?why?带着疑问去看alloc源码
上一章提到alloc 源码核心方法是 _class_createInstanceFromZone,这个方法做了3件事

  1. cls->instanceSize(extraBytes): 计算需要开辟多大的内存空间
  2. (id)calloc(1, size): 申请内存,返回地址指针
  3. obj->initInstanceIsa(cls, hasCxxDtor) :将类与isa指针关联

这一章我们分析一下内存

instanceSize源码

    size_t instanceSize(size_t extraBytes) const {
        //编译器快速计算内存大小
        if (fastpath(cache.hasFastInstanceSize(extraBytes))) {
            //加断点走到这里
            return cache.fastInstanceSize(extraBytes);
        }
        //计算类中所有属性的大小 + extraBytes(这时候是0)
        size_t size = alignedInstanceSize() + extraBytes;
        // CF requires all objects be at least 16 bytes.
        //如果size 小于 16,取16 ,16对齐
        if (size < 16) size = 16;
        return size;
    }

fastInstanceSize源码

  size_t fastInstanceSize(size_t extra) const
    {
        ASSERT(hasFastInstanceSize(extra));

        if (__builtin_constant_p(extra) && extra == 0) {
            return _flags & FAST_CACHE_ALLOC_MASK16;
        } else {
            //加断点走到这里
            size_t size = _flags & FAST_CACHE_ALLOC_MASK;
            // remove the FAST_CACHE_ALLOC_DELTA16 that was added
            // by setFastInstanceSize
            return align16(size + extra - FAST_CACHE_ALLOC_DELTA16);
        }
    }

align16 16字节对齐算法

static inline size_t align16(size_t x) {
    return (x + size_t(15)) & ~size_t(15);
}

总结
NSObject alloc后,开辟的内存大小在计算完所有属性大小后,还要再来个16字节对齐!malloc_size获取的就是对象实际占用的内存大小

那么新的问题来了, 为什么要16字节对齐?原因有以下几点

  • 通常内存是由一个个字节组成的,cpu在存取数据时,并不是以字节为单位存储,而是以块为单位存取,块的大小为内存存取力度。频繁存取字节未对齐的数据,会极大降低cpu的性能,所以可以通过减少存取次数来降低cpu的开销
  • 16字节对齐,是由于在一个对象中,第一个属性isa占8字节,当然一个对象肯定还有其他属性,当无属性时,会预留8字节,即16字节对齐,如果不预留,相当于这个对象的isa和其他对象的isa紧挨着,容易造成访问混乱(苹果早期是8字节对齐,现在新的系统是16字节对齐)
  • 16字节对齐后,可以加快CPU读取速度,同时使访问更安全,不会产生访问混乱的情况

结构体内存对齐

//1、定义两个结构体
struct Mystruct1{
    char a;     //1字节
    double b;   //8字节
    int c;      //4字节
    short d;    //2字节
}Mystruct1;

struct Mystruct2{
    double b;   //8字节
    int c;      //4字节
    short d;    //2字节
    char a;     //1字节
}Mystruct2;

两个结构体除了变量顺序不一样以外都一样,结果确不一样,这就是因为内存对齐

8字节对齐规则

每个特定平台上的编译器都有自己的默认“对齐系数”(也叫对齐模数)。程序员可以通过预编译命令#pragma pack(n),n=1,2,4,8,16来改变这一系数,其中的n就是你要指定的“对齐系数”。在ios中,Xcode默认为#pragma pack(8),即8字节对齐

结构体内存对齐原则

  1. 数据成员的对齐规则可以理解为min(m, n) 的公式, 其中 m表示当前成员的开始位置, n表示当前成员所需要的位数。如果满足条件 m 整除 n (即 m % n == 0), n 从 m 位置开始存储, 反之继续检查m+1 能否整除 n, 直到可以整除, 从而就确定了当前成员的开始位置
  2. 数据成员为结构体:当结构体嵌套了结构体时,作为数据成员的结构体的自身长度作为外部结构体的最大成员的内存大小,比如结构体a嵌套结构体b,b中有char、int、double等,则b的自身长度为8
  3. 最后结构体的内存大小必须是结构体中最大成员内存大小的整数倍,不足的需要补齐。

结构体内存对齐验证


Mystruct1的内存分布

Mystruct2的内存分布

验证一下原则2,定义一个Mystruct3

struct Mystruct3{
    char a;     //1字节
    struct Mystruct1 struct1;
}Mystruct3;

总结
在定义结构体的时候,根据内存从大到小的顺序定义,可以省内存空间
那么NSObject 会不会有这个问题呢?继续

NSObject内存优化

定义一个继承于NSObjectPerson

@interface Person : NSObject

@property (nonatomic, copy) NSString * name;
@property (nonatomic, assign) int age;
@property (nonatomic, assign) int height;

@end


person对象的内存分布并不是像结构体那样,第一个是isa指针,第二个不是name,第三个是name,把第二个分开打印,发现第二个里面实际存储了ageage,顺序都变化了,实际是苹果帮我们做了内存优化,属性重排

person的内存分布

接下来我算下person的大小,isa指针8字节,age、height 8字节,name8字节,再根据16字节算法,开辟的内存大小32?打印结果如下

总结
计算属性内存的大小时class_getInstanceSize按照8字节对齐计算,但是真正开辟的内存空间大小malloc_size是按照16字节对齐计算

你可能感兴趣的:(OC 对象内存探索(内存对齐))