0x01:编程是什么
· 认识我们要控制的角色
谈到编程,说到底就是控制计算机按照一定的逻辑将某些数值去做运算的过程。
- 负责运算的角色叫CPU。
- 数据的记录就需要占用存储空间,这些存储空间又叫缓存。
- 根据缓存的读写速度及重要程度,又划分了一级缓存、二级缓存、三级缓存···
1 一级缓存:CPU读写的数据要有地方存,这些“地方”就是寄存器。
2 二级缓存:受CPU规模所限,就那么几个寄存器,只能挑一些指令必要的数据存储,剩下的程序必要数据可以放到CPU外面——内存。
3 三级缓存:由于还有一些数据需要断电后被记录,且对程序运行来讲不是那么重要,所以就有了更廉价的存储器——硬盘。
至此,我们编程所需要关心的主要角色都到齐了。
一般情况下,需要我们要控制的东西有:CPU、寄存器、内存。
所用的原力就是:高低电平。可以用二进制表示成“01001”这种指令。
· 简化我们的魔法书
我们是人类啊,记一句“急急如律令”还行,“000111010101”这些东西怎么记?于是乎,聪明的人类对魔法书进行了第一次修改:人们在魔法书上列了一张2 x n的表格,左边是0101,右边就是MOV、ADD等指令。魔法书从此改名了,叫汇编。
· 丰富多彩的魔法书
随着社会的发展,编程需求越来越多,代码体量越来越大,汇编代码越来越不好调试、管理、维护,于是聪明的人们有开始想办法了。
不就是要写指令、管理内存嘛!指令再用更通俗的词、句标识,内存的管理往数学模型上靠,于是就有了数据结构、高级指令,有机的结合在一起,新的魔法书诞生了:语言。出于不同的目的和需要,魔法书从此丰富多彩:C、Java、C#、Python、Dart。。。
· 机器怎么识别新魔法
语言是给我们看的,计算机看不懂,它需要一个翻译器。什么时候翻译?
- 从本本上读一句,给机器解释一句:这就是脚本语言。
- 把本本1上的所有句子翻译完,写成本本2,交给机器自己看:这就是编译语言。
0x02:浅谈iOS编程
· 我们写的代码是给谁看的
以前为了验证autorelease相关的理论,用clang翻译了一下OC代码,看到基本的都是runtime的东西。那一刻我突然明白了一个道理:你以为编译器(具体指编译器后端)要翻译的代码就是你写的代码吗?不是的!我们写的代码不是给机器看的,是给clang前端看的。 等到编译器准备把上层代码翻译成汇编的时候,这些代码早就不是我们写的模样了,全被clang翻译成了c、c++代码。
所以面试的时候人家会问你:“KVO原理是什么?Block原理是什么?” 而不是问:“KVO源码是什么?Block源码是什么?” 因为上层KVO、block代码,是被clang按照固定格式重写成了一大坨c++代码,这个固定格式的写法其实就是原理。并没有KVO、Block源码一说。
· 我们写的代码是什么意思
之前因为逆向,学习了一段时间汇编,至此我才真正明白了内存、指针、对象以及我写的代码是什么意思。
举个简单的例子:
/// Student.h
@interface Student : NSObject
@property (nonatomic, assign) long age;
@end
/* 代码案例 */
Studen *s = [[Student alloc] init];
/// 这里的s是什么?是对象?还是指针?
/// 如果想不明白,可以想想s、&s、*s分别是什么?存储着什么?
/// 对象是什么?对象存在哪里?你有对象了么?(咳咳,最后这个是开个玩笑)
假如你已经思考过了,那现在来看结论吧。请务必先思考一下。
[[Student alloc] init];//在堆内存上开辟一块空间,不考虑内存对齐,大小是Student对应结构体的大小。
Student *s = Student对象; //创建一个Student *类型的指针,指针s的值是Student对应结构体的首地址(也就是isA)。
结论:
(lldb) po &s
(Student **) $0 =0x00007ffee3ca0878
(lldb) p s
(Student *) $1 = 0x00006000027ec910
(lldb) po s
<Student: 0x6000010e4840>
(lldb) p *s
(Student) $0 = (_age = 0)
综上 可以发现:
- "&s":是Student * * 类型的指针
- {
- "(lldb) p s"一行:证明s是Student * 类型的指针,就是它所占的8个字节
- "(lldb) po s"一行:证明把s当成对象去用,也没有问题
- }
- *s:是指针s所指向的数据结构全部。代表一整段连续内存
结合runtime源码,我们可以知道,一个对象其实就是一个结构体,是一段连续的内存。s指针存储着这个结构体首地址,使得我们可以访问这段连续的内存。
如果s是局部变量,那就在栈区上,如果是全局变量,那就在全局区(静态区);
堆、栈、代码段、数据段、静态区···这些概念都是我们为了方便管理内存,虚拟出来的一些区域。甚至连我们平常所说的内存都是虚拟的。
这些概念就是约定好的协议。有了内存管理的这些协议,大家都去遵守,那我们就可以进一步宏观化,,根据人们的不同需求抽象出C语言、操作系统、网络协议等等。
上面代码的内存分布,如下图所示:
0x03 再思考一下编程的本质
有了C语言的结构体,我们可以更好地管理内存,定义好一个结构体里面的字段以及字段的类型,那整个结构体的大小就有了。当我们初始化一个结构体的时候(即OC中new一个对象),编译器帮我们翻译成汇编:开辟一段固定长度的内存。
我们再也不用在编程的时候思考:
我要开辟多少个内存单元?
我们要对这段内存的第几个单元写什么值?(比如char类型'A',ascII码对应着65,需要给内存写入65)
可见有了编译器、有了语言,我们可以更方便的管理内存,控制逻辑。
我们编程的本质也从对CPU、寄存器、内存的控制,转到了对数据类型、对象、逻辑的控制。
好了,就啰嗦这些。本人内功有限,如果有理解不到位的地方,强烈欢迎大家提出批评建议。