OC 对象原理探索(二)

目录

  • 1. 对象的内存影响因素
  • 2. 成员变量在内存中的存储情况
  • 3. 结构体内存对齐
     3.1 类型占用字节数表格
     3.2 内存对齐原则
       1、数据成员对齐规则
       2、结构体作为成员
       3、结构体总大小
     3.3 举例验证
  • 4. sizeofclass_getInstanceSizemalloc_size
  • 5. malloc源码分析
     5.1 源码下载
     5.2 源码分析
  • 6. 总结

1. 对象的内存影响因素

先创建一个SSLPerson类:

@interface SSLPerson : NSObject {
    NSString *_hobby;
}

@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *nickName;

- (void)eat;

@end

打印一下它的内存大小:

nickName属性注释掉:

//@property (nonatomic, copy) NSString *nickName;

查看打印结果:

eat方法注视掉:

//- (void)eat;

查看打印结果:

把成员变量_hobby注视掉:

//    NSString *_hobby;

查看打印结果:

通过上面的打印,我们发现属性成员变量影响了对象内存,方法没有影响,所以我们可以得出最终结论:只有成员变量会影响对象内存。

2. 成员变量在内存中的存储情况

我们修改一下SSLPerson类:

@interface SSLPerson : NSObject

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

@property (nonatomic) char a;
@property (nonatomic) char b;

@end

创建SSLPerson类的实例p,为属性赋值,用x/8gx命令打印出这些值在内存中的存储情况:

找出ssl王老五180.5

找出24ab

注:aASCII码97,bASCII码98

通过打印结果我们发现24ab被存储到了同一个8字节内,这里就涉及到了内存对齐的一些原则,下面继续探究。

3. 结构体内存对齐

3.1 类型占用字节数表格

C OC 32位 64位
bool BOOL(64位) 1 1
signed char (__signed char)int8_t、BOOL(32)位 1 1
unsigned char Boolean 1 1
short int16_t 2 2
unsigned short unichar 2 2
int int32_t NSInteger(32位)、boolean_t(32位) 4 4
unsigned int boolean_t(64位)、NSUInteger(32位) 4 4
long NSInteger(64位) 4 8
unsigned long NSUInteger(64位) 4 8
long long int64_t 8 8
float CGFloat(32位) 4 4
double CGFloat(64位) 8 8

3.2 内存对齐原则

1、数据成员对齐规则

结构体struct或联合体union的数据成员,第一个数据成员放在offset0的位置,以后每个数据成员存储的起始位置要从该成员大小或者成员的子成员大小(只要该成员有子成员,比如说数组,结构体等)的整数倍数开始(比如int4个字节,则要从4的整数倍地址开始存储)。

2、结构体作为成员

如果一个结构里有某些结构体成员,则结构体成员要从其内部最大元素大小的整数倍地址开始存储,(struct a中存有struct bb中有charintdouble等元素,那么b应该从8的整数倍开始存储),因为double为最大子元素,占用8个字节。

3、结构体总大小

结构体总大小,也就是sizeof的结果,必须是其内部最大成员整数倍,不足的要补齐。

3.3 举例验证

struct Struct1 {
    double a;       // 占8字节 存放在[0 7]
    char b;         // 占1字节 下一个索引8是1的整数倍,存放在[8]
    int c;          // 占4字节 下一个索引9不是4的整数倍,所以空出9,10,11,存放在 [12 13 14 15]
    short d;        // 占2字节 下一个索引16是2的整数倍,存放在[16 17]
}struct1;           // 总区间为[0...17],大小为18,取最大元素double8字节的整倍数,所以总大小为24

struct Struct2 {
    double a;       // 占8字节 存放在[0 7]
    int b;          // 占4字节 下一个索引8是4的整数倍,存放在[8 9 10 11]
    char c;         // 占1字节 下一个索引12是1的整倍数,存放在[12]
    short d;        // 占2字节 下一个索引13不是2的整倍数,所以空出13 存放在[14 15]
}struct2;           // 总区间为[0...15],大小为16,取最大元素double8字节的整倍数,所以总大小为16

struct Struct3 {
    double a;               // 占8字节 存放在[0 7]
    int b;                  // 占4字节 下一个索引8是4的整数倍,存放在[8 9 10 11]
    char c;                 // 占1字节 下一个索引12是1的整数倍,存放在[12]
    short d;                // 占2字节 下一个索引13不是2的整数倍,所以空出13,存放在[14 15]
    int e;                  // 占4字节 下一个索引16是4的整数倍,存放在[16 17 18 19]
    struct Struct1 str1;    // 占24字节 下一个索引20不是str1中double8字节的整数倍,所以空出20 21 22 23,最后存放在[24.....47]
    struct Struct2 str2;    // 占16字节 下一个索引48是str2中double8字节整数倍,存放在[48.....64]
    short f;                // 占2字节 下一个索引65不是2的整数倍,所以空出65,存放在[66,67]
}struct3;                   // 总区间为[0...67],大小为68,取最大元素double 8字节的整倍数,所以总大小为72

NSLog(@"Struct1:%lu -- Struct2:%lu -- Struct3:%lu",
            sizeof(struct1), sizeof(struct2), sizeof(struct3));

查看打印结果:

Struct1:24 -- Struct2:16 -- Struct3:72

4. sizeofclass_getInstanceSizemalloc_size

修改SSLPerson类:

@interface SSLPerson : NSObject

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

@end

main.m中代码:

#import "SSLPerson.h"
#import <objc/runtime.h>
#import <malloc/malloc.h>

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        SSLPerson *person = [SSLPerson alloc];
        person.name      = @"lee";
        person.nickName  = @"ssl";
        
        NSLog(@"person %@",person);
        NSLog(@"sizeof %lu",sizeof(person));
        NSLog(@"person %lu",class_getInstanceSize([SSLPerson class]));
        NSLog(@"person %lu",malloc_size((__bridge const void *)(person)));
    }
    return 0;
}

查看打印结果:

解释:

  • sizeof这里计算的是person指针的大小,指针统一为8字节;
  • class_getInstanceSize计算的是isa指针加成员变量占用的内存:name(NSString``8字节) + nickName(NSString``8字节) + age(int``4字节) + height(long``8字节) + isa(来自NSObject``8字节) = 36字节,按照8字节对齐,最终为40字节;
  • malloc_size计算的是实际向系统申请开辟的内存空间:40字节向系统申请时,遵循16字节对齐原则,最终为48字节。

malloc是如何申请内存的呢,我们接下来通过源码来进行分析。

5. malloc源码分析

5.1 源码下载

工程中点击malloc_size定位源码所在位置:

malloc 源码下载地址,我们以317.40.8版本为例进行分析。

5.2 源码分析

我们在main.m中添加代码,用40字节去申请内存:

#import <Foundation/Foundation.h>
#import <malloc/malloc.h>

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        // 用 40 字节去申请内存
        void *p = calloc(1, 40);
        NSLog(@"开辟字节数:%lu",malloc_size(p));
    }
    return 0;
}

查看打印结果:

确实还是开辟了48字节的空间,下面通过断点调试查看源码。

断点进入calloc

断点进入:_malloc_zone_calloc

通过返回值找到核心代码ptr = zone->calloc(zone, num_items, size),点击进入calloc(zone, num_items, size)发现找不到方法实现。

可以通过po的方式找到应该调用的方法default_zone_calloc

  • 为什么可以打印获取:因为有赋值,就会有存储值,就可以打印输出。
  • 第二种方式:除了输出的方式,还可以通过汇编找方法的方式找到方法的真实调用。

断点进入default_zone_calloc

还是找不到方法,通过p zone->calloc获取到方法nano_calloc

注:ppo打印的更详细。

断点进入nano_calloc

根据返回值定位核心代码:_nano_malloc_check_clear,断点进入:

根据返回值找到segregated_size_to_fit关键函数,断点进入:

这里是16字节对齐算法,40经过对齐后得到48,这就是最终会开辟48字节的原因。

6. 总结

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

推荐阅读更多精彩内容