OC对象底层探索 — 由字节对齐到对象内存的分配

用于记录iOS底层学习,以备后续回顾

OC对象底层探索

前言

我们继续来探索对象是如何申请、开辟、优化内存大小的。
要想了解对象的内存优化首先要知道内存对齐原则(理论加实践一点点的搞懂)。

一、内存对齐

1.1 内存对齐的三个规则

  • a. 数据成员对齐规则:结构体(struct)(或联合体(union))的数据成员,第一个数据成员放在offset为0的地方,以后每个数据成员存储的起始位置要从该成员大小或者成员的子成员大小(只要该成员有子成员,比如数组、结构体等)的整数倍开始(比如int为4字节,则要从4的整数倍地址开始存储)。
  • b. 结构体作为成员:如果一个结构里有某些结构体成员,则结构体成员要从其内部最大元素大小的整数倍地址开始存储(struct a里存有struct b,b里有char,int,double等元素,那b应该从8的整数倍开始存储)
  • c. 最后判断:结构体的总大小,也就是sizeof的结果,必须是其内部最大成员的整数倍,不足要补齐

1.2 内存对齐代码示例探究

经过实际探究,下列示例已涵盖内存对齐的全部原则并加以备注

struct DZStruct1 {
    char a;     // 1    [0]
    double b;   // 8    [8, 15]
    int c;      // 4    [16, 19]
    short d;    // 2    [20, 22]
} MyStruct1; // sizeof(MyStruct1) = 24字节

struct DZStruct2 {
    double b;   // 8    [0, 7]
    char a;     // 1    [8]
    int c;      // 4    [12, 15] 按顺序从9开始,但是起始位置需要从该成员大小的整数倍开始,所以起始位置9需要往大了走,到12为4的整数倍
    short d;    // 2    [16, 17]
} MyStruct2; // sizeof(MyStruct2) = 24字节,结构体的总大小,必须是其内部最大成员的整数倍,不足要补齐所以要从17补齐到24为8的整数倍

struct DZStruct3 {
    char a;     // 1    [0]
    struct DZStruct1 struct1;   // 24   [8, 31] 按顺序从1开始,但是起始位置需要从该结构体(DZStruct1)内部最大元素大小的整数倍开始存储,所以从1往大了走,到8为8的整数倍
    double b;   // 8    [32, 39]
    short d;    // 2    [40, 41]
} MyStruct3; // sizeof(MyStruct3) = 48字节

1.3 字节基础补充

a. 先简单补充一点字节相关基础知识:
sizeof()是运算符,编译的时候就是一个确定的数据会替换为常数,返回的是一个类型所占内存的字节大小。

b. 了解获取内存的三个方法

  • sizeof是运算符,编译的时候就替换为常数,返回的是一个类型所占内存的大小
  • class_getInstanceSize传入一个类对象,返回一个对象的实例至少需要多少内存,它等价于sizeof,需要导入#import <objc/runtime.h>
  • malloc_size返回系统实际分配的内存大小,需要导入#import <malloc/malloc.h>

二、探索对象申请内存和系统分配内存

2.1 首先对无成员变量对象进行探索

DZTeacher.h文件

#import <Foundation/Foundation.h>
@interface DZTeacher : NSObject

main.m文件

#import <Foundation/Foundation.h>
#import "DZTeacher.h"
#import <objc/runtime.h>
#import <malloc/malloc.h>
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        DZTeacher  *p = [DZTeacher alloc];
        NSLog(@"申请内存大小为:%lu——-系统开辟内存大小为:%lu",class_getInstanceSize([p class]),malloc_size((__bridge const void *)(p)));
    }
    return 0;
}
分析结果:无成员变量对象应该分配内存:isa的8字节(isa后续会进行详细探索)
实际打印结果 : 
申请内存大小为:8——-系统开辟内存大小为:16

class_getInstanceSize 内部实现如下:

size_t class_getInstanceSize(Class cls)
{
    if (!cls) return 0;
    return cls->alignedInstanceSize(); // 直接返回字节对齐大小
}

回忆上一篇文章, alloc 初始化往下探索其中有一个方法_class_createInstanceFromZone,内部会调用size_t size = cls->instanceSize(extraBytes),具体实现如下:

size_t instanceSize(size_t extraBytes) {
    size_t size = alignedInstanceSize() + extraBytes;
    // CF requires all objects be at least 16 bytes.
    if (size < 16) size = 16;  // 最小返回16
    return size;
}

我们通过查看OC源码可知,class_getInstanceSize返回类的成员变量占据内存的大小,而malloc_size获取obj指针指向内存的大小
分析得出:系统在创建一个对象的时候,对象的isa指针占据8个字节,但是系统会为其分配最少16字节的内存空间,所以如果该对象没有成员变量, class_getInstanceSize 会输出 8 个字节,malloc_size 会输出最少 16 个字节。

2.2 添加成员变量后对象内存探索

DZTeacher.h文件

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN
@interface DZTeacher : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) int age;
@property (nonatomic, assign) long height;
@property (nonatomic, strong) NSString *hobby;

main.m文件

#import <Foundation/Foundation.h>
#import "DZTeacher.h"
#import <objc/runtime.h>
#import <malloc/malloc.h>
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        DZTeacher  *p = [DZTeacher alloc];
        // isa ---- 8
        p.name = @"Dezi";   // sizeof(p.name) = 8
        p.age  = 18;            // 4
        p.height = 185;         // 8
        p.hobby  = @"女";       // 8
        NSLog(@"%@",p);
        NSLog(@"申请内存大小为:%lu——-系统开辟内存大小为:%lu",class_getInstanceSize([p class]),malloc_size((__bridge const void *)(p)));
    }
    return 0;
}
分析结果:
1. 成员变量应该分配内存:8字节 + 4字节 + 8字节 + 8字节 = 28字节
2. 再加上isa的8字节 = 36字节(isa后续会进行详细探索)
3. 根据字节对齐原则最大8字节,所以36往大走补充到40字节
4. 所以内存空间应该分配40字节
5. 注意:对象开辟空间的时候成员变量就会编译进来,所以成员变量未赋值也会分配内存

实际打印结果 : 
申请内存大小为:40——-系统开辟内存大小为:48

根据上方打印信息,我们的分析是对的,类对象至少需要40字节,那为什么实际分配内存大小为48字节呢?下来我们继续探索。

2.2 calloc探索

经过对源码的一步步探索,我们发现,在obj = (id)calloc(1, size);这个方法的时候,对象内存大小发生了变化

calloc方法探索

malloc.png

根据上图我们发现calloc方法在malloc源码里边,那我们打开新的源码继续分析:

DZCallocTest.png

之后进入calloc流程,首先调用malloc_zone_calloc方法

calloc.png

在其内部调用zone->calloc初始化并且返回了一个ptr指针

malloc_zone_calloc.png

断点在此处我们发现递归了,那我们在这里打印zone->calloc,我们找到malloc.c文件249行的default_zone_calloc方法

zone->calloc.png

加上断点我们发现此处又是zone->calloc,继续打印zone->calloc发现nano_malloc.c文件中878行的nano_calloc方法

default_zone_calloc.png

在malloc的源码中搜索nano_calloc,于nano_calloc.c文件中找到该方法,其中的核心代码_nano_malloc_check_clear进行内存申请,并且返回一个指针p

zone->calloc.png

_nano_malloc_check_clear内部发现segregated_size_to_fit方法输入的size是40,输出的是48,所以这个方法是开辟内存的算法

_nano_malloc_check_clear.png

segregated_size_to_fit方法进行分析,发现对齐原则是16字节对齐,所以输入实际需要的40,经过16字节对齐后输出的为48

segregated_size_to_fit.png
nano_zone_common.png

2.3 总结+流程图

对象内存大小的申请是按照8字节对齐,不满16字节时按照16字节计算;若大于16字节时,calloc实际开辟内存则是按照16字节对齐。
感觉8字节对齐是为了属性之间内存安全提高容错空间,16字节对齐是为了保证对象之间内存安全提高容错空间。

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

推荐阅读更多精彩内容

  • 前话: 在了解内存对齐之前先了解一下各数据类型在内存中的大小,目前我们比较常用的是64位系统,所以我们的研究对象统...
    sz_蓝天使者阅读 706评论 0 3
  • 前言 iOS底层探索之对象原理(一)中了解到通过calloc我们对象有了内存地址,通过initInstanceIs...
    litongde阅读 220评论 0 2
  • iOS底层探索 - 内存补齐 在上篇文章中我们主要探索了对象的初始化以及怎么开辟内存。内存对齐三大原则是什么?对象...
    leng_li阅读 267评论 0 0
  • 作者:李振君 这个孤僻的泥瓦匠 只会把沉默砌成墙 什么也不说; 他唯一的爱好就是 收工后 用满腹的心事 让一个酒瓶...
    李振君阅读 231评论 0 4
  • 今天爸爸和妈妈都上班,只有我和姐姐在家里。 早上我们醒来的时候。我和姐姐就泡了方便面,吃完了之...
    王启萱阅读 314评论 0 0