我在课本上学的汇编是枯燥的,学了不知道干什么 。但其实汇编可以用来分析一些编译型语言的本质 。我们可以通过查看编程语言官方说明文档进行学习,再通过汇编语言彻底理解其机理。
首先要明白,编译型语言→(编译器)→汇编语言→(编译器)→机器语言,我们使用c/c++作为例子来分析。 在vs2010/2017上标注断点后进入反汇编模式,即可查看汇编代码,这里我们在Windows上通过vs查看的是Intel汇编代码,此外xcode也可以查看AT&T汇编代码,Linux和Uinux可以查看另一种汇编代码,总之汇编代码有很多种,但是内核一致。
分析1: ifelse和Switch哪个效率高
由汇编代码可知,ifelse的原理是不断进行compare,然后j跳转。如果传入的值靠后,则需要比较很多次。而switch的原理是先用一些代码计算出传入值对应switch中的那一个case,然后直接跳转到对应的case内执行语句。
这样看来,如果判断大量离散点,使用switch是一个不错的选择,因为不需要一一比较,只有经过计算选一个case即可。但是如果要判断连续区间或者复杂条件,则可以使用ifelse判断。
ps.使用ifelse时我们可以把出现频率高的内容放到前面,先进行判断。 但是,我们前面分析的是连续的离散点用switch比较好,此时我们计算出各个case语句的地址然后跳转过去即可,例如1.2.3.4.5…。
case1 地址1
case2 地址2
case3 地址3…
default 地址n
(这些case是连续储存的,其地址其实存放在另一块内存,我们使用jmp [address]实现跳转到一个地址里存放的另一个地址,jmp address则是直接跳转到这个地址。)
(在实现最终跳转case的jmp之前还有一个比较cmp已经ja跳转,就是计算一下我们输入的值减去最小的值以后的那个数,有没有我们最大case减最小case那个数大,如果大,则直接通过ja跳转到default,无需接下来的选择)
如果是有间隔的离散点呢?离散点的间隔大小对switch的底层实现有没有影响呢? 当间隔小点离散点例如1.3.6.9…时,编译器底层会会把间隙的地址中放入default情况。
case1 1
default a 2
default 3
case3 4
default 5…
如果间隔很大,编译器就不会这么优化了,因为每次在一个间隔插入一个default就会占用4个字节的内存,我们在牺牲内存减少时间。
间隔很大时我们开辟了一个新的空间:
case从最小到最大连续时有多少个离散点就开辟多少空间,每个空间内存放一个字节。这些空间地址与case值一一对应,如果case是我们给出的则正常编号如00/01/02/03…,把正常的case编好号以后剩余的都赋值一个相同的、比正常编号最后一位多1点编号。
拿到这个编号,再做先前的运算,就能确保正常的case与其对应的语句一一对应,没有写出的case情况都跳转的default。
这种方法把原先增加一个default消耗4B优化为增加一个default消耗1B。
当case的间隔再大呢?这时编译器就自动将其转换为ifelse比较-跳转模式。当case个数较少时编译器也会转换switch为ifelse方式。
分析2: a++和++a
其实++a就是把a赋值给一个寄存器,对寄存器操作做其他运算,最后再把a放到寄存器然后实现把a加一。a++就是把a赋值给一个寄存器,然后实现把a加一,然后再把a赋值给一个寄存器,对寄存器操作做其他运算。
本质区别就是什么时候把a的值赋给寄存器,对寄存器操作做其他运算。
带图笔记:
汇编分析解释型语言重点语法.