References:
alloc和init 方法的作用:
首先创建studyAlloc类,并且创建几个对象如下列代码所示,我们来总结一下规律:
#import <Cocoa/Cocoa.h>
#import "studyAlloc.h"
int main(int argc, const char * argv[]) {
studyAlloc *a0 = [studyAlloc alloc];
studyAlloc *a1 = [studyAlloc alloc];
studyAlloc *a2 = [a1 init];
studyAlloc *a3 = [a1 init];
// 对象的内容 - 对象的地址 - 对象指针地址
NSLog(@"a1: %@ - %p - %p", a0, a0, &a0);
NSLog(@"a1: %@ - %p - %p", a1, a1, &a1);
NSLog(@"a2: %@ - %p - %p", a2, a2, &a2);
NSLog(@"a3: %@ - %p - %p", a3, a3, &a3);
return NSApplicationMain(argc, argv);
}
运行结果:
2021-07-16 14:16:46.485997+0800 superMaster[89247:3230214] a0: <studyAlloc: 0x600002278280> - 0x600002278280 - 0x110958458
- 2021-07-16 14:16:46.486139+0800 superMaster[89247:3230214] a1: <studyAlloc: 0x600002278290> - 0x600002278290 - 0x110958450
2021-07-16 14:16:46.486187+0800 superMaster[89247:3230214] a2: <studyAlloc: 0x600002278290> - 0x600002278290 - 0x110958448
2021-07-16 14:16:46.486221+0800 superMaster[89247:3230214] a3: <studyAlloc: 0x600002278290> - 0x600002278290 - 0x110958440
从运行结果可以看出:
- 1: a1, a2, a3 对象的内容,对象的地址相同,但是对象指针的地址却不相同
- 2: a0 和 a1的这三项内容都不一样
内存结构:
我们可以看出
- 1:调用alloc可以开辟新的内存,而调用init不会
- 2:栈分配的空间是从高到低连续,堆分配的空间是从低到高连续
定位源码:
opensource.apple.com/ opensource.apple.com/tarballs/
底层探索的三种方法:
- 符号断点直接跟流程
符号断点: Symbolic Breakpoint为符号断点,可以针对某一个方法(函数)设置断点并暂停执行;有时候,我们并不清楚会在什么情况下调用某一个函数,那我们可以通过符号断点来跟踪获取调用该函数的程序堆栈。 首先在需要调试的地方加断点,然后再加一个符号断点alloc
点击运行,发现停在了[NSObject alloc], 说明studyAlloc的alloc方法继承自NSObject的alloc方法.
然后按住 control和step-into,停在了
说明alloc方法来自于 libobjc.A.dylib库.
- 按住control - step into
首先在需要调试的地方加断点
点击运行,停在了如下图所示的位置
然后按住 control和step-into,停在了
我们发现停在了objc_alloc这个方法上,然久就把objc_alloc加到符号断点上
重新运行工程,停在了
这样我们也可以知道alloc调用了哪个库函数
- 汇编查看跟流程
首先依然在需要调试的地方加断点,然后点击运行,
点击Debug->Debug Workflow->Always Show Disassembly
停在了第10行,然后在12行的位置打一个断点
停在第12行之后,按住control和step-into,停在了
然后把objc_alloc加在符号断点上,就可以看出alloc调用的是哪个库函数了
编译器优化
准备汇编指令: juejin.cn/post/697813…
编写代码并加断点:
点击运行,并且查看汇编代码
- 数字前面的w代表32位寄存器, x代表64位寄存器
- int类型只有4个字节最多占用32位,所以使用w寄存器 用指令register read来读寄存器:
-
可以看w9的值就是10,mov w9, #0xa的意思是讲0xa赋值给w9
-
stur w9, [x29, #-0x14] 的意思是将w9的内存取出来, 存到 [x29, #-0x14] 这个栈地址上, 也就是 [x29, #-0x14] = &a
-
str w9, [sp, #0x18] 的意思是将w9的内存取出来, 存到 [sp, #0x18] 上, 就是 [sp, #0x18] = &b
-
ldur w0, [x29, #-0x14] 把10取出来放到寄存器w0 里面
-
ldr w1, [sp, #0x18] 把20取出来放到寄存器w1 里面
-
bl 0x1024cdfac; lgSum at main.m:15 跳转到函数lgSum 然后打开编译器优化, 选项切换到Fastest, Smallest:
再次查看汇编代码
可以看出少了很多中间步骤。 关于编译器优化级别:
-
None[-O0]: 不优化。在这种设置下, 编译器的目标是降低编译消耗,保证调试时输出期望的结果。程序的语句之间是独立的:如果在程序的停在某一行的断点出,我们可以给任何变量赋新值抑或是将程序计数器指向方法中的任何一个语句,并且能得到一个和源码完全一致的运行结果。
-
Fast[-O1]: 大函数所需的编译时间和内存消耗都会稍微增加。在这种设置下,编译器会尝试减小代码文件的大小,减少执行时间,但并不执行需要大量编译时间的优化。在苹果的编译器中,在优化过程中,严格别名,块重排和块间的调度都会被默认禁止掉。
-
Faster[-O2]: 编译器执行所有不涉及时间空间交换的所有的支持的优化选项。在这种设置下,编译器不会进行循环展开、函数内联或寄存器重命名。和‘Fast[-O1]’项相比,此设置会增加编译时间和生成代码的性能。
-
Fastest[-O3]: 在开启‘Fast[-O1]’项支持的所有优化项的同时,开启函数内联和寄存器重命名选项。这个设置有可能会导致二进制文件变大。
-
Fastest, Smallest[-Os]: 优化大小。这个设置开启了‘Fast[-O1]’项中的所有不增加代码大小的优化选项,并会进一步的执行可以减小代码大小的优化。
-
Fastest, Aggressive Optimizations[-Ofast]: 这个设置开启了“Fastest[-O3]”中的所有优化选项,同时也开启了可能会打破严格编译标准的积极优化,但并不会影响运行良好的代码。
alloc 底层流程
流程如图所示:
其中核心方法为
instanceSize, calloc, initInstanceIsa
- instanceSize:计算出所需要的内存空间大小
- calloc:向系统申请开辟内存, 返回地址指针
- initInstanceIsa:关联到相应的类