2020-01-06汇编初相识

听了李明杰老师的《MJ教你玩汇编》公开课_1 学习笔记

2020/1/4 18:33

国内很多非一手的资料都是错的,包括一些知名的网站都是错的,所以在查看资料时要保持怀疑的态度,不要全信。

我们应该最好先查看一手资料,也就是官方文档,但是官方文档有时候也有错的地方,因为都是人写的嘛,推荐顺序:官方文档->翻译资料->技术博客,百度等。

一个软件/程序 首先是装在硬盘/磁盘上,内存加载程序的执行代码,cpu读取内存里的指令,CPU根据指令再控制显示器等其他硬件工作。

cpu里有个寄存器,运行速度比内存快,计算的时候一般都是在寄存器操作的,然后再把值回传给内存。

为什么要了解汇编语言:

汇编语言可以做什么:

1.通过分析汇编语言,我们可以分析内存,cpu在干什么,以及性能等等 。

2.汇编语言和机器语言可以互相转换,一一对应,但是汇编语言很难转换成高级语言,因为高级语言的多种写法对应的汇编语言可能是一样的,例如:c++定义一个结构体并赋值,和定义一个数组并赋值,最终我们看他的汇编代码都是一样的。

3.汇编语言可以玩破解,外挂。。。

1.语言主要有两大块

编译语言 ,脚本语言

编译语言:写的代码必须全部正确才能通过编译,高级语言(C,C++,OC,SWIFT)编写好后,编译器可以直接转成汇编语言,编译语言比脚本语言更高效。JAVA也是编译语言,但是中间需要转换成cclass字节码,再转换成编译语言,需要支持虚拟机(虚拟机可以想象成一个小的操作系统),虚拟转换成汇编,再转换成机器语言。

脚本语言:

即使编写的代码有误,程序也能运行,因为它是运行时再一行一行的加载的,有误的话不加载这行和代码就行

脚本语言是有一个自己的脚本引擎转换,最后转换成的机器语言我们看不到,比如。HTML CSS,是网页自带的编译器转换,最后只是显示在网页上,我们看不到机器语言。

不管哪个语言,最终都会转换成机器语言给CPU执行,不同语言只是适用不同的应用场景,比如做网页的话,html就比较合适,如果用C的话,就会很麻烦。

语言随时都会有更新,所以需要理解了语言的本质,,理解了本质,其实就是0和1的事,理解了汇编语言,无论什么高级语言都只是写法不同,方法不同罢了。

听了李明杰老师的《MJ教你玩汇编》公开课_2 学习笔记

2020/1/4 22:55

首先介绍下CPU的指令集:

百度百科了解了下, 针对手机,90%以上都是采用ARM的微处理器,iOS手机用的指令集,iphone5以下用的是armv7,5用的是armv7s,cpu指令集都是向下兼容的,所以armv7s是向下兼容armv7的,iPhone5以上就是armv64指令集的cpu了。

其中,armv7,armv7s是32位的,armv64是64位的。

PC呢,主要有X86, AT指令集, 一般windows的电脑是X86的指令集,MAC电脑是AT指令集,AT指令集一般出现在Linux操作系统中。

如果我们使用Xcode反编译查看汇编代码会发现其格式是AT指令集格式的,但是不管是AT还是X86,他们只是表达方式不一样,其根本是一样的,因为都是要最终转换成机器语言。

我们要研究代码执行的效率,

一般之前用的方法是,写一段测试代码,在代码开头和结尾定义当前系统时间,最终2个时间差来比较代码执行效率。

但是上面这个方法有很多弊端:

1.要写测试代码

2.效率非常依赖机器的性能

3.当前执行代码的时机跟CPU的当前繁忙程度有很大关联,

所以我们主要通过分析反编译后的汇编代码来分析效率,因为执行一个指令的时间几乎一样,可以分析代码的指令个数多少来判断其效率高低。

举例:if-else & switch 比较谁的执行效率高?

查看汇编代码,if-else 是判断每个条件,成立则执行,不成立则继续判断,如果成立的条件比较靠后,那么判断的次数就会很多,这样CPU的指令集数量就会升高。

而switch,会根据当前的值,直接找到要执行的分支case,虽然前面会多了几行指令,但是在判段条件比较多,而成立的值写得很靠后的情况,switch比if-selse的指令集数量更少,效率更高。

但是swich一般只能判断特定的数值,(这个跟语言特性有关,OC不能,但是SWIFT可以) 像区域和字符串等就不能判断。但是我们在可以能够SWITCH替代的地方尽量替代。

如果要使用if-selse,可以把高概率发生的条件写在前面,这样相对可以降低判断次数。

空间换时间:空间指的是内存,意思是消耗更多的内存来减少代码执行的时间

时间换空间:消耗更多的时间来节约对内存的消耗,像一些嵌入式的,比如单片机,内存本来就少,这时候既不能开辟内存了,只能等待更长的时间来执行命令

switch有点以空间换时间的意思,但是消耗的那点内存很少,不值一提。

听了李明杰老师的《MJ教你玩汇编》公开课_3 学习笔记

2020/1/5 00:46

听了李明杰老师的《MJ教你玩汇编》公开课_4 学习笔记

2020/1/5 11:47

这节课主要分析switch代码反编译后的汇编代码 ,编译器是如何优化switch的,

把switch的所有case代码地址存在另一个开辟的地址中,这个开辟的地址里存的就是各case需要执行的代码,只需判断一次变量找到这个开辟的地址,从而执行开辟地址里的地址的指令。

如果case比较少,汇编代码结构跟if-selse是差不多的

还有就是当case的跨度很大时,汇编代码的结构也跟if-else差不多

只有当case跨度不是特别离谱,跨度的临界值每个编译环境可能不太一样,也不确定,这是编译器自己内部分析的事情。

通过汇编代码我们可以看到,switch的优化是这样的:

有以下几种情况:

1.当跨度是有规律的时候  得到偏移值 =  当前值 - 最小值

首先会拿当前值与最大值和最小值最比较,如果不在区间中,直接跳转default代码地址。

如果在区间中,第一个case的代码地址 +  偏移值*sizeof(**( 值的类型)),从而得到最终代码执行地址

2.如果无规律,且跨度不是很大时,会把中间跨度的值最终指向deafult代码地址,

3.无规律,且跨度很大,但是在编译器可优化的范围内时,会把偏移量用一个字节保存起来,会遍历所有的跨度区间里的值,每一个值的偏移用一个字节保存,这样以一个字节比之前用一段地址去保存deafult代码地址更节约内存。

最后了解一下 a++ ,++a,的汇编代码

a++:a的值放入寄存器,寄存器的值去参与后面的其他运算,参与其他运算后的值回传给他的接受者,最后其他运算结束后,a本身再+1,

++a: a本身先做加1计算,计算后把值回传给a,这时a的值已经发生改变了,然后再拿a的值去参与其他的运算。

听了李明杰老师的《MJ教你玩汇编》公开课_5 学习笔记

2020/1/5 12:59

c++出发了解下构造函数,

网上谬论,c++默认会自己创建构建构造函数,其实这句话是错的。

我们通过反编译,查看汇编代码可以验证,新建一个类,里面什么事情也不做,是没有执行构造函数的

但是只有在一些特定情况下,编译器才会为我们创建构造函数

当一个类创建时有事情需要做时:

1.代码写了构造函数

2.成员变量赋了值,其实就是语法糖,变向等于程序员自己写了构造函数,并在构造函数对成员变量赋值

3.类中有成员类,成员类中有构造函数

4.父类有构造函数。

最后疑难解答提到一点,函数和方法有没有区别,

有些人会说,方法是定义在类中的,函数是定义在外面的。

通过汇编代码查看,无论是定义在类中的,还是定义在外面的,其实都是最后执行一段代码地址,内存中有一个人区域叫代码段,

所以函数和方法是没有区别的,都是函数,定义在类中可以叫成员函数。

有这种区分的人可以说是中了语法糖的毒,看的是一些语法,概念性,表面的东西,没有深入其本质去窥探。

其实外面接触的资料,里面有很多的信息都是错的,上面的信息有些是个人经验总结,或者打印看是这么一个结果,而看信息的人,大家没有验证其正确性的方式,不懂怎么验证,所以盲目相信。其实大家都知道怎么验证了,验证一下就知道到底说的对不对。

听了李明杰老师的《MJ教你玩汇编》公开课_6 学习笔记

2020/1/5 15:43

这堂课主要讲解了函数的栈空间

首先了解下内存,内存没有回收和释放一说,内存是不断复写的,

栈:栈顶,栈底 esp ebp

调用一个函数,如果有参数。首先会把参数压栈push,栈顶指针向上偏移

然后汇编里有个叫ret的函数调用,他会把下个地址压栈,(压地址),接着我们F11单步调试汇编代码,栈底指向栈顶的位置,栈顶再向上+地址,加的内存就是给这个函数使用的内存,再这段内存里,会对函数内的变量进行运算,也就是局部变量,计算后赋值,计算完成后,会按照之前相反的操作,栈顶指针向下偏移,之前为函数分配的内存相当于不被使用了,里面的值还是保持不变,只不过下次用时里面的值可能会被重写,然后,再把最先压入的参数pop出栈,然后把栈底的地址赋值给栈底,栈顶回到最先函数执行的地方,大致过程是这样。

如果函数内部有调其他函数,过程也是一样,继续往上分配内存给函数,return的最后把栈顶指针回到函数最先开始执行的地方。

栈溢出,

举例,递归函数,如果没有函数出口,或者函数出口的条件很深,那么在执行函数时,就会不断的分配内存,但是内存毕竟是有限的,不断的分配内存总会有分配完的时候,这时候就叫栈溢出了,stack overflow

你可能感兴趣的:(2020-01-06汇编初相识)