OC底层原理2之alloc内存开辟大小,类、结构体内存对齐

alloc内存开辟大小

这块内容随便记记不要慌张

我们知道alloc内部执行的时候(objc源码,进行开辟内存的函数为calloc)然后去探究malloc库的源码或者探究汇编进行查找其执行的函数有哪些

执行代码打断点查看汇编断点的时候跟alloc流程一样它又是进符号绑定函数,这步直接略过没啥看的

void *p = calloc(1, 40);
NSLog(@"%lu",malloc_size(p));
//输出结果是48,这是为什么呢
复制代码

汇编分析

  1. 查看calloc函数,其内部调用了_malloc_zone_calloc函数

image.png
2.看 _malloc_zone_calloc 函数,发现他执行的第一个函数地址竟然不是静态的,而是rbx+0x20地址。那么我们是不是可以想着它内部为何会这样呢我们先进去看看侯发现他继续执行的函数是default_zone_calloc

image.png
3.看 default_zone_calloc 函数内部有很多跳转判断,这就很麻烦了,经过我一步步单步它走到了_os_once_callout但是感觉这么奇怪呢,我搜索这个函数应该可以继续跳过,好家伙找到了_malloc_initialize函数,感觉这个真贴切。但是他妈的竟然内容这么多

image.png
4. _malloc_initialize 函数,真让人裂开了。我可能忽略了某一步,借助汇编到这里就算了,然后查看源码我们肯定能理清它的调用方式

image.png

malloc源码分析

1.我们根据源码再结合汇编看到我们可以直接到_malloc_zone_calloc这个函数内部侯看到标红的代码这是怎么回事,然后我在lldb中断点后进行po zone->calloc 后发现它指向的函数是default_zone_calloc这不正是汇编查到的吗

image.png
2. 查看default_zone_calloc函数发现和上面一样我们继续lldb操作然后又把矛头指向了nano_calloc

image.png
3. 查看 nano_calloc 函数 发现一个重要代码,我有理由相信total_bytes就是大小了,继续查看_nano_malloc_check_clear函数

image.png
4. 查看 _nano_malloc_check_clear 函数就可以得知其中最重要的代码就是ptr的赋值代码,ptr就是内存地址,但是我们研究的目标是calloc所开辟的内存大小

image.png
5.也就是第四步中 _nano_malloc_check_clear 函数中也有一句代码调用了一个函数

image.png
6.我们看到其内部就是16字节对齐的方式,这就是为什么对象的大小为什么16字节对齐。

image.png

类、结构体内存对齐

扩展:

1.sizeof是操作符,计算数据类型的大小,在编译阶段就已经确定其大小

2.class_getInstanceSize该函数是runtime的一个方法,传入的是Class。而Class就是类,在底层中它实际是objc_class类型的结构体指针,而objc_class实际继承于objc_object结构体

3.malloc_size是系统分配的实际内存,他是malloc库的函数

类内存

类内存大小受成员变量和属性的影响,但不受方法的影响,因为方法没有在类内存里。

类的内存对齐方式在iOS中是16字节对齐,而类中的成员变量是8字节对齐。造成该对齐方式原因是因为苹果对于其进行了内存优化。所以平时开发中针对类的时候可不必考虑内存问题。

例如:
可以看到Person类有两个NSString属性,它的内存我们可以得知是isa+8+8,也就是刚好24个字节,然后我们根据系统对象16字节对齐方式,它就成了32个字节。

Person *person = [Person alloc];
person.name      = @"a";
person.nickName  = @"a";
NSLog(@"----数据类型大小--%lu---%lu", sizeof(person), sizeof([Person class]));
NSLog(@"----对象内存实际占用大小--%lu", class_getInstanceSize([Person class]));
//NSLog(@"----系统分配的内存大小--%lu", malloc_size(CFBridgingRetain(person)));
NSLog(@"----系统分配的内存大小--%lu", malloc_size((__bridge const void *)(person)));
复制代码

打印:

----数据类型大小--8---8
----对象内存实际占用大小--24
----系统分配的内存大小--32
复制代码

补充:

例如给Person再加一个double类型的属性height,和一个char类型的c。

那么其class_getInstanceSize函数进行计算的结果就是isa + 3*8 + 1 = 33,然后8字节进行对齐,结果就是40。(为什么会8字节进行对齐,这是前面提到的类成员变量的内存对齐),而系统给开辟的实际内存是malloc_size 结果是48(16字节对齐)

结构体内存

结构体的对齐方式是根据其数据成员来。具体规则如下:

image.png

例如:有两个结构体数据成员一样,但是由于顺序不一样就会造成了内存不一样,所以这是内存优化的一方面(内存优化还包括,数据的存储位置、定义类型等)

struct Struct1 {
    double a;       // 8字节   
    char b;         // 1字节    
    int c;          // 4字节    
    short d;        // 2字节    
}struct1;

struct Struct2 {
    double a;       // 8    
    int b;          // 4    
    char c;         // 1    
    short d;        // 2    
}struct2;

NSLog(@"%lu-%lu",sizeof(struct1),sizeof(struct2));
复制代码

结果:struct1的大小为24字节,struct2大小为16字节

------24-16
复制代码

造成这的原因是因为其顺序不一致。按照结构体内存对齐的方式的1,3原则我们得到如下

struct1为 (内存0-7存的是a,8存的是b,12-15存的是c,16-17存的是d。然后根据内存对齐原则为最大内存double类型8字节的整数倍。则为24)

image.png

然后再看一个结构体里套结构体的例子:得出结构体str是其内部最大内存数据double a为基础开始进行排序,所以要在其当前内存序号16 是 8 的整数倍开始的,但是也可以直接得出一个结论:struct4的内存大小40 = 除结构体外其他成员变量结构的总和大小16(应为其内部最大的double的整数倍) + str的内存大小24

struct Struct4 {
    int b; //4 [0 3]
    char c; // 1 [4]
    short d;// 2 (5 [6 7]
    int e;// 4 [8 11]
    struct Struct1 str; // 24 (12 13 14 15 [16 39] 40 //从8 的整数倍开始存
}struct4;

复制代码
© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享