美文网首页iOS底层
OC内存大小的相关计算

OC内存大小的相关计算

作者: NeroXie | 来源:发表于2020-02-14 17:19 被阅读0次

原文链接OC内存大小的相关计算

更新于2020-07-13

在面试的过程中,我们较大概率地会被问一个类所占的内存大小。本篇博客从下面一段测试代码开始分析整个内存大小的计算过程。

测试代码如下:

struct A {
} TestA;

struct AA {
    char a;
} TestAA;

struct AAA {
    char a;
    int b;
} TestAAA;

@interface Person: NSObject {
    int _a;
}

@end

@implementation Person
@end

@interface Student1: Person {
    int _b;
}

@end

@implementation Student1
@end

@interface Student2: Person {
    int _b;
    int _c;
}

@end

@implementation Student2
@end

// 测试
+ (void)test {
    NSLog(@"TestA sizeof: %lu",sizeof(TestA));
    NSLog(@"TestAA sizeof: %lu",sizeof(TestAA));
    NSLog(@"TestAAA sizeof: %lu",sizeof(TestAAA));
    NSLog(@"--------------------------------------");
    NSLog(@"NSObject class_getInstanceSize = %zd", class_getInstanceSize([NSObject class]));
    NSLog(@"NSObject malloc_size = %zd", malloc_size((__bridge const void*)[NSObject new]));
    NSLog(@"--------------------------------------");
    NSLog(@"Person class_getInstanceSize = %zd", class_getInstanceSize([Person class]));
    NSLog(@"Person malloc_size = %zd", malloc_size((__bridge const void*)[Person new]));
    NSLog(@"--------------------------------------");
    NSLog(@"Student1 class_getInstanceSize = %zd", class_getInstanceSize([Student1 class]));
    NSLog(@"Student1 malloc_size = %zd", malloc_size((__bridge const void*)[Student1 new]));
    NSLog(@"Student1 sizeof = %zd", sizeof([Student1 class]));
    NSLog(@"--------------------------------------");
    NSLog(@"Student2 class_getInstanceSize = %zd", class_getInstanceSize([Student2 class]));
    NSLog(@"Student2 malloc_size = %zd", malloc_size((__bridge const void*)[Student2 new]));
}

执行结果(运行在模拟器下)如下:


class_getInstanceSize&malloc_size

其中class_getInstanceSize指的是成员变量占用的内存大小,malloc_size指的是指针指向内存空间的大小即实际分配的内存大小。

关于内存大小的计算主要依赖于运行环境以及内存对齐。上面的都是运行在arm64环境下,因此内存对齐决定了它们的值为什么不同。

内存对齐

内存对齐说白了就是为了提高CPU寻址操作性能的一种规则。我们可以通过#pragma pack(n),n=1、2、4、8、16 来改变这一系数,其中的n就是要指定的“对齐系数”。内存对齐的规则如下:

  1. 数据成员对齐规则:结构体或联合体的第一个数据成员放在偏移为0的位置,以后每个数据成员的位置为min(对齐系数,自身长度)的整数倍,下个位置不为本数据成员的整数倍位置的自动补齐。
  2. 数据成员为结构体:该数据成员的内最大长度的整数倍的位置开始存储。
  3. 整体对齐规则:数据成员按照1,2步骤对齐之后,其自身也要对齐,对齐原则是min(对齐系数,数据成员最大长度)的整数倍。

内存对齐计算

在64位编译器环境下

代码示例1:

// 对齐系数为8
#pragma pack(8)
struct AA {
    int a;   // 4字节
    char b;  // 1字节
    short c; // 2字节
    char d;  // 1字节
} Test1AA;
#pragma pack()

int main(int argc, char * argv[]) {
    @autoreleasepool { 
        NSLog(@"Test1AA: %lu",sizeof(Test1AA));
    }
}

执行结果:

Test1AA: 12

计算过程如下:


内存对齐计算1

代码示例2:

#pragma pack(8)
struct AA {
    char a[2];
    short b;
    struct BB {
        int a;
        double b;
        float c;
    } Test2BB;
} Test2AA;
#pragma pack()

int main(int argc, char * argv[]) {
    
    @autoreleasepool {
        NSLog(@"Test2AA: %lu",sizeof(Test2AA));
    }
}

执行结果:

Test2AA: 32

计算过程如下:


内存对齐计算2

OC类的内存分析

相关函数

class_getInstanceSize

上面讲到class_getInstanceSize表示成员变量所占的内存大小,那么对于Person类创建的实例来说,它的成员变量大小应该为12,为什么结果却是16。

class_getInstanceSize的内存也有它自己的内存对齐,通过objc源码中的class_getInstanceSize的底层实现可以知道,class_getInstanceSize的实现依赖于底层函数word_align,该函数返回的结果是8的倍数,另外从alloc函数开始进行分析,到instanceSize函数中可以知道所有对象的内存大小至少是16个字节,所以对象申请的内存空间是以8字节进行内存对齐且至少是16个字节。

word_align实现如下:

static inline uint32_t word_align(uint32_t x) {
    // WORD_MASK在64下的定义为7UL,就是7,所以相当于(x + 7) & ~7
    // 0000 0111    -> 7
    // x:12,12就是Person类中成员变量的大小
    // 12+7 = 19
    // 0001 0011    -> 19
    // &
    // 1111 1000    -> ~7  ~运算,二进制中,0变1,1变0. 
    // 0001 0000    -> 16
    return (x + WORD_MASK) & ~WORD_MASK; 
}

malloc_size

通过malloc源码中的segregated_size_to_fit函数可以知道系统开辟内存空间是以16字节进行内存对齐。

static MALLOC_INLINE size_t
segregated_size_to_fit(nanozone_t *nanozone, size_t size, size_t *pKey) {
    size_t k, slot_bytes;
    if (0 == size) {
        // NANO_REGIME_QUANTA_SIZE: (1 << SHIFT_NANO_QUANTUM) 即 16
        size = NANO_REGIME_QUANTA_SIZE;
    }
    
    // size: 8 
    // 0000 1000   -> 8
    // size + NANO_REGIME_QUANTA_SIZE - 1 = 8 + 15 = 23
    // 0001 0111   -> 23
    // >> 4
    // 0000 0001
    // << 4
    // 0001 0000   -> 16
    k = (size + NANO_REGIME_QUANTA_SIZE - 1) >> SHIFT_NANO_QUANTUM;
    slot_bytes = k << SHIFT_NANO_QUANTUM;
    *pKey = k - 1;

    return slot_bytes;
}

Person、Student的内存分析

回到一开始的测试代码,根据上面内存对齐的3个规则,Person、Student1、Student2的实际分配内存(malloc_size)计算过程如下:


内存对齐计算3

使用View Memory查看内存

xcode_view_memory

从上面这张图中我们可以知道,两个16进制代表1个字节,一行有32个字节。01 00 00 0003 00 00 0005 00 00 00(小端)对应的就是_a_b_c,另外前8个字节就是isa。整体内存布局也与我们手动计算实际内存分配的结果一致。

sizeof

sizeof用来返回类型的大小,其内部也是进行了内存对齐的。将测试代码中的结构体AAAAAA通过内存对齐规则进行分析,确实得到018

这里我们使用结构体AA进行举例:

  • 根据规则1,数据成员占1个字节,位于0号地址;
  • 无结构体成员变量,跳过规则2;
  • 根据规则3,min(1, n) = 1,取1的整数倍,即结构体分配的内存大小为1个字节。

关于使用sizeof去获取OC类内存大小的时候,我发现一个比较有意思的东西。

测试代码如下:

struct N_NSObject_IMPL {
    Class isa;
};

struct N_Person_IMPL {
    struct N_NSObject_IMPL NSObject_IVARS;
    int _a;
};

struct N_Student_IMPL {
    struct N_Person_IMPL Person_IVARS;
    int _b;
    char _c;
};

@interface Person : NSObject {
    int _a;
}

@end

@implementation Person
@end

@interface Student : Person {
    int _b;
    char _c;
}

@end

@implementation Student
@end

// 测试
+ (void)test {
    NSObject *o = [NSObject new];
    struct N_NSObject_IMPL *so = (__bridge struct N_NSObject_IMPL *)o;
    NSLog(@"[NSObject new] sizeof = %lu", sizeof(o));
    NSLog(@"[NSObject class] sizeof = %lu", sizeof([NSObject class]));
    NSLog(@"struct N_NSObject_IMPL sizeof = %lu", sizeof(struct N_NSObject_IMPL));
    NSLog(@"*so sizeof = %lu", sizeof(so));
    NSLog(@"--------------------------------------");
    Person *p = [Person new];
    struct N_Person_IMPL *sp = (__bridge struct N_Person_IMPL *)p;
    NSLog(@"[Person new] sizeof = %lu", sizeof(p));
    NSLog(@"[Person class] sizeof = %lu", sizeof([Person class]));
    NSLog(@"struct N_Person_IMPL sizeof = %lu", sizeof(struct N_Person_IMPL));
    NSLog(@"*sp sizeof = %lu", sizeof(sp));
    NSLog(@"--------------------------------------");
    Student *s = [Student new];
    struct N_Student_IMPL *ss = (__bridge struct N_Student_IMPL *)s;
    NSLog(@"[Student new] sizeof = %lu", sizeof(s));
    NSLog(@"[Student class] sizeof = %lu", sizeof([Student class]));
    NSLog(@"N_Student_IMPL sizeof = %lu", sizeof(struct N_Student_IMPL));
    NSLog(@"Student malloc_size = %zd", malloc_size((__bridge const void*)s));
    NSLog(@"*ss sizeof = %lu", sizeof(ss));
}

执行结果:

打印sizeof

N_NSObject_IMPLN_Person_IMPLN_Student_IMPL是将OC转成C++代码时对应的结构,使用sizeof获取这些结构体大小的时候,值与class_getInstanceSize的值是一样的。

使用sizeof获取指针、实例、类其结果都是8,这又是为什么呢?个人认为传实例和类的时候可以看做传的其实就是对应的指针。指针的值是指针本身存储的数值,这个值将被编译器当作一个地址,地址基本就是整形,因此无论用什么类、对象作为sizeof的参数(这里就将sizeof看成是一个函数),其结果都一样的。

最后再总结下class_getInstanceSizemalloc_sizesizeof的区别:

  • class_getInstanceSize表示成员变量的所占的内存大小
  • malloc_size表示实际分配的内存大小
  • sizeof表示变量或者类型的大小,传入结构体,返回的则是结构的大小,传入指针(这里的指针表示C指针,OC的引用)即传入值,则返回传入值的类型大小

相关文章

  • OC内存大小的相关计算

    原文链接OC内存大小的相关计算 更新于2020-07-13 在面试的过程中,我们较大概率地会被问一个类所占的内存大...

  • 内存对齐

    知识点概要 OC对象内存对齐结构体内存对齐 OC对象内存对齐 计算内存大小的三种方式 1.sizeof:系统提供的...

  • OC底层原理03-内存对齐

    该文章是对前一篇 OC底层原理01-alloc流程探索 中cls->instanceSize(计算内存大小)分支的...

  • iOS calloc

    浅究 calloc oc 代码中的 alloc 主要有三种作用 1、计算分配内存大小(字节对齐)2、向系统申请内存...

  • iOS 内存对齐探究

    影响OC对象内存大小的因素 数据类型内存大小: 代码分析 通过class_getInstanceSize获取实例的...

  • iOS 字节对齐

    前言:在上一篇文章中,我们了解到,OC 底层的alloc 到底做了哪些事情。总结起来就是: 1、计算内存大小 2、...

  • NSObject 底层本质

    一、OC 转 C/C++ 二、NSObject 对象内存布局 三、NSObject 内存大小 四、OC 对象内存布...

  • iOS底层系列06 -- OC对象的内存对齐与分配

    在阐述OC对象内存对齐之前,我们先来看个实例代码 上述代码的运行结果如下: 同一个对象,计算对象内存大小的三种方式...

  • 如何计算Java对象所占内存的大小

    摘要 本文以如何计算Java对象占用内存大小为切入点,在讨论计算Java对象占用堆内存大小的方法的基础上,详细讨论...

  • 如何计算Java对象所占内存的大小

    摘要 本文以如何计算Java对象占用内存大小为切入点,在讨论计算Java对象占用堆内存大小的方法的基础上,详细讨论...

网友评论

    本文标题:OC内存大小的相关计算

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