2021-06-10

iOS 内存对齐

文章目录


最近遇到一个问题 下面person 占用多少的内存?

Person *person = [[Person alloc] init];

一开始的分析如下:

NSLog@(@"%zd".class_getInstanceSize([Person class])); // 打印==8
NSLog(@"%zd",malloc_size((__bridge const void *) person)); // 打印16

一个打印8一个打印16 那个才是person的真实的占用内存大小呢? 我们通过上篇文章分析的到oc对象的本质是一个结构体并且会继承父类NSObiectisa,已知一个isa 的指针式并且占用8个字节?怀着疑问先找到了内存对齐的规则如下:

1. 内存对齐 基本规则

1.数组成员对齐规则: 结构体或联合体的数据成员,第一个数据成员放在offset为0的地方,以后每个数据成员存储的其实位置要从该成员的大小或子成员大小(只要该成员有子成员,比如说是数组和结构体等)的整数倍开始
例如:

int 为 4 字节,则要从4的整数倍地址开始存储
假如:当前开始位置在8 则int的存储
则为:9 10 11 12
假如:开始位置为9
则为:10(空) 11(空) 12(为4的整数倍则开始存储) 13 14 15 

2.结构体作为成员: 如果一个结构里有某些结构体成员,则结构体成员要从其内部最大元素大小的整数倍地址开始存储
例如:

struct a {
    double a_a,    [0,7]
    int a_b, [8]
    char a_c, [9]
    struct b, (10,11,12,13,14,15)16+16=32
}a1 32字节

struct b {
    double b_a, //8 字节    [0,7]
    int  b_b, //4 字节      [8,9,10,11]
    char c_c, //1 字节      [12]
}b1 16字节
结构体a 包含结构体b,结构体 b里边最大的是8字节 则存储开始位置应该从8的整数被开始

3.结构体的总大小,必须是其内部最大的整数倍,不足的需要补齐
iOS基本数据类型 字节对照表


在知晓内存对齐规则后结合我们之前分析能的到Person在**c++**代码中的实现应该如下:
结合内存对齐规则 我们可以暂时的出结论Person 所占用的内存是8字节 但是这个16是怎么来的我们需要翻阅一下iOS的源码看下能否得出结论Objc源码 需要编译

struct Person_IMP {
	Class isa;
}

2.分析iOS objc源码内存分配做了什么?

// 我们可以找到如下代码
_class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone,
                              int construct_flags = OBJECT_CONSTRUCT_NONE,
                              bool cxxConstruct = true,
                              size_t *outAllocatedSize = nil)
{
    ASSERT(cls->isRealized());

    // Read class's info bits all at once for performance
    bool hasCxxCtor = cxxConstruct && cls->hasCxxCtor();
    bool hasCxxDtor = cls->hasCxxDtor();
    bool fast = cls->canAllocNonpointer();
    size_t size;
	// 我们可以看出这行代码就是在获取内存大小的 
    size = cls->instanceSize(extraBytes);
    if (outAllocatedSize) *outAllocatedSize = size;

    id obj;
    if (zone) {
        obj = (id)malloc_zone_calloc((malloc_zone_t *)zone, 1, size);
    } else {
    // 这里是分配内存
        obj = (id)calloc(1, size);
    }
    if (slowpath(!obj)) {
        if (construct_flags & OBJECT_CONSTRUCT_CALL_BADALLOC) {
            return _objc_callBadAllocHandler(cls);
        }
        return nil;
    }

    if (!zone && fast) {
    // 关联isa指针
        obj->initInstanceIsa(cls, hasCxxDtor);
    } else {
        // Use raw pointer isa on the assumption that they might be
        // doing something weird with the zone or RR.
        obj->initIsa(cls);
    }

    if (fastpath(!hasCxxCtor)) {
        return obj;
    }

    construct_flags |= OBJECT_CONSTRUCT_FREE_ONFAILURE;
    return object_cxxConstructFromClass(obj, cls, construct_flags);
}
inline size_t instanceSize(size_t extraBytes) const {
// 查阅资料的到这句话的意思是快速计算内存
// 在objc2 中基本上都是快速计算
        if (fastpath(cache.hasFastInstanceSize(extraBytes))) {
            return cache.fastInstanceSize(extraBytes);
        }
/// 
        size_t size = alignedInstanceSize() + extraBytes;
        // CF requires all objects be at least 16 bytes.
        if (size < 16) size = 16;
        return size;
    }
   
 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
            // 总这里我们可以看出来iOS 内存分配内存需要16位对齐的 
            return align16(size + extra - FAST_CACHE_ALLOC_DELTA16);
        }
    }

结论: 从以上代码中我们可以看出iOS 对象内存分配是16位对齐的这也就解释了我们开始的问题.

// 获取NSObject 的成员变量所占的字节
NSLog@(@"%zd".class_getInstanceSize([Person class])); // 打印==8
// 获取该对象指针指向的内存大小
NSLog(@"%zd",malloc_size((__bridge const void *) person)); // 打印16

如今问题又来了 为什么需要16位对齐呢?
1.如果是8字节对齐,假设一下,我们创建任意对象都会有isa 指针一个isa 指针8个字节,也就是说多个没有属性的对象,那么它们的内存空间就会完全的挨在一起,容易混乱。从提高寻址效率和内存空间综合考虑。

上一篇:drf请求生命周期分析


下一篇:iOS-底层原理 17:类的加载(上)