首先我们先来简单的回忆一下内存分区:
代码区:存放代码,可读,可执行。
栈区:参数、局部变量、临时数据。
堆区:动态申请,可读、可写。
全局区:全局变量,可读、可写。
常量区:只读。
-
常量区 & 全局区
首先我们来看下面的代码:
test
对应的汇编代码如下:
通过上图,我们得知,在我们汇编代码的断点位置:
i
:x0
寄存器里面存放的是,常量Aaron
的地址值。
ii
:地址值里面存放的是ASCII
码。
那么问题来了:这个地址值是怎么来的?
其实通过逻辑推理不难判断,此时x0
里面的值是通过下面这两条指令获得的:
0x1021ea0f8 <+16>: adrp x0, 1
0x1021ea0fc <+20>: add x0, x0, #0xf85 ; =0xf85
下面我们来解释一下这两条指令:
0x1021ea0f8 <+16>: adrp x0, 1
这句指令的意思是:
①:将`1`左移`12位`, 得到的结果是:`1000`(注意一个字节是4位)
②:将`pc`寄存器里面的值的后12位清零,然后加上上面的结果,得到的结果是:`0x1021eb000`
此时`x0`的值是`0x1021eb000`
0x1040f60fc <+20>: add x0, x0, #0xf85
这句指令的意思是:
①:将`x0`与`0xf85`相加
②:将相加的结果再存入`x0`
最终`x0`的值是`0x1021ebf85`
这样就得到了我们的字符串常量Aaron
。
其实我们的全局变量g
也是这样获得的:
讲到这里,可能就有人要问了,
adrp
指令里面的1
/7
这些又是什么意思呢?其实1
就代表4K
,怎么来的呢?
首先我们说了:
adrp
中1
要左移12位
,就是1000
。
那么这3个0
换成F
就是FFF
,正好是4095
。0~4095
是4096
个数,正好是4K
- tips:
MacOS
(M1
除外)中,内存的一页是4K
;iOS
中一页是16K
。这个我们也可以通过终端指令来看一下:
循环选择和判断
首先我们先来认识一个新的指令:cmp(Compare)
比较指令
-
CMP
把一个寄存器的内存
和另一个寄存器的内存
或立即数
进行比较。但是不存储结果,只是正确的更改标志。
其实就是进行减法
,但是不影响当前寄存器内容,只是修改标记寄存器
。
一般CMP
做完判断后会进行跳转,后面通常会跟上B
指令⚠️ 。 -
bl
标号:跳转到标号处执行。 -
b.gt
标号:比较结果是大于(greater than)
,执行标号,否则不跳转。 -
b.ge
标号:比较结果是大于等于(greater than or equal to)
,执行标号,否则不跳转。 -
b.eq
标号:比较结果是等于
,执行标号,否则不跳转。 -
b.hi
标号:比较结果是无符号大于
,执行标号,否则不跳转。
接下来我们借助另一个工具Hopper
来查看汇编代码,为反汇编做准备。
-
if else
有这么一段代码,编译之后将可执行文件拖入Hopper
中查看(注意这里我们是用真机编译的,ARM64
)
w8 & w9
存储的就是我们的a & b
,这里就不不做过多的分析。注意:我们代码中写的是
if(a >b)
;cmp
做减法的结果如果是非正数
就跳转至:loc_100006100
;如果是正数
,就接着往下执行。
- 如果是
非正数
,则loc_100006100
代码块执行完毕之后,会直接进入loc_100006108
代码块。() - 如果是
正数
,则继续执行接下来的代码块,然后通过b
指令,跳转到loc_100006108
代码块。
这就是if else
指令的汇编体现。同时我们又认识了一个新的指令:b.le
(小于等于)。
再认识一个:b.lt
(小于)
loop(循环)
- 首先我们来看一下
doWhile
可以看到,doWhile
的汇编指令,先执行do
,然后根据b.lt
指令进行跳转或者继续执行。 -
while
-
for (int i = 0; i < 100; i++)
可以发现,普通的
for
循环的汇编指令跟while
没有什么区别。
注意 ⚠️ :
for in
的汇编代码就要复杂很多,有兴趣的同学可以自己编译查看一下。
-
Swith
⚠️ 注意:此时我们只写了3个判断,并且还是连续的
可以看到,当判断条件在3个及以内的时候,switch
的汇编实现就是if else
。
我们再来添加一个case
:
case 4:
printf("1");
break;
我们会发下,当判断条件大于3个的时候,
swith
的汇编代码就变了,不再是之前的if else
。这就是swith
底层优化,如果判断条件大于3个的时候,就会创建一个表,表里面存储了每一个case
对应的要执行的代码的地址。只要去查表就可以了。(当然,前提条件是,判断条件是连续的有规律的(或者差值不是很大的)。)
下面我们看一下无序的switch
:
switch (a) {
case 0:
printf("1");
break;
case 10:
printf("1");
break;
case 2:
printf("1");
break;
case 40:
printf("1");
break;
default:
break;
}
会发现,此时
switch
又变成了if else
。
我们在使用
switch
的时候,要注意case
的数量,以及case
的规律,这样才能提高代码的质量。
这里我们只是简单的探讨了一下switch
,后续我们会针对switch
做一下更深入的研究。