OC内存大小的相关计算

原文链接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的引用)即传入值,则返回传入值的类型大小
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,340评论 5 467
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,762评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,329评论 0 329
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,678评论 1 270
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,583评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 47,995评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,493评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,145评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,293评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,250评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,267评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,973评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,556评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,648评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,873评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,257评论 2 345
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,809评论 2 339

推荐阅读更多精彩内容