16位汇编第六讲汇编指令详解第二讲
1.比较指令
CMP指令
1.CMP指令是将目的操作数减去源操作数,按照定义相应的设置状态标志
2.CMP指令执行的功能与SUB指令(相减指令)一样,但是不同的是CMP指令之根据结果设置标志位
而不修改值
可以操作的指令格式
CMP reg,imm/reg/mem
CMP mem,imm/reg
上面是CMP指令的语法,具体的也可以查询帮助文档,inter手册
inter手册查的办法
第一个框代表了CMP指令的所有语法
比如
reg,reg 表示可以比较寄存器 CMP AX,BX ....
下面的则是机器的操作码.根据二进制的机器码可以反逆向出来汇编指令
比如:
机器码是39代表的是CMP指令
一般CMP的指令,都是设置标志位的,然后一般会和别的指令成对执行,比如比较完毕就判断结果.
汇编例子
cmp al,100 jz below ;al == 100会跳转到below执行(jz下面将,这里理解为跳转)
2.CPU的流水线,汇编的无分支,以及优化
什么是CPU的流水线,这样说吧,上面我们说了,CMP会和跳转一起使用,但是你知道这样的代码吗?
使用一条跳转,可以执行很多条指令.CPU的指令周期很长,这里说一下强制跳转JMP
看下JMP
可以看出,最快的需要15个指令周期,最慢的需要24 + (EA:寻址方式,的有效寻址的周期)大小
那么我们可以优化一下吗
比如 C语言中的三目运算符 a == 0 ? 0:-1; 如果a < 0,成立,则a = 0,否则= -1
如果在汇编中你想怎么写, 是不是先判断ax == 0? 如果 ==0 ,我就跳转到0的地方,执行,否则跳转到-1的地方执行
最少需要两个跳转是吗
这样浪费了很多指令周期
所以我们可以写成这样
mov ax,3 neg ax sbb ax,ax
我敢说,学过汇编的人从来都只是说学过,而汇编是一门艺术,我们学习汇编,并不是学习他的指令或者语法,比如上面简单的三条汇编指令,会汇编的人都能明白,但是我想问一下,什么意思懂吗?
这个就是无分支三目运算符
简单看一下吧
ax = 3 (浪费了4个指令周期,因为有立即数,要内存寻址,所以该浪费的还是得浪费)
neg ax 这个是对3求补,上堂课也说了,汇编的求补指令的原理就是 0 - 操作数(ax))的结果
0 - 3的话,计算机也不会算,所以要把-3变为补码,让0去相加
原码: 1000 0011 (负数的源码最高位是1)
反码 1111 1100 (反码最高位不变,其余各位取反)
补码: 1111 1101 (-3的补码)
0 + 上补码
0000 0000
+
1111 1101
1111 1101 (还是负数)
在计算机中表示 FD 此时CF位被设置,因为计算机判断进位的方法就是看最高位,如果最高位以前是0,那么经过计算最高位为1,则判断进位了.
CF = 1
SBB ax,ax
带借位的相减
首先ax的值就是FD了,两个相减没了,但是注意,这里有符号位,所以相减的同事要把符号位减去
1111 1101
-
1111 1110(需要加上符号位1,那么二进制就成为了这种)
= F (-1)那么看上面的推理过程
结果就是给一个3,对齐求补码,然后算出结果为-1
当然这个只是教怎么玩汇编,不过分析程序的时候可能遇到这种优化
CPU的流水线
上次我们说了一个JMP指令,指令周期特别长,我们可以优化成上面的那样,但是仅仅是优化了吗?
我们不妨这样想,上面的确实是优化了,但是其实内部还有CPU的流水线的优化
什么是流水线
比如一个工厂,组装汽车的,分为三步骤
第一步,取配件 (取指令)
第二步,组装 (译码(解析指令))
第三步,喷漆 (CPU执行)
我们需要三个工人,可以这样想,第一个人专门取配件,第一次执行的时候,组装的喷漆的都等待
当配件拿到手了,那么开始组装(这个时候第一个人又去取配件了),这时候喷漆的等着
当给了喷漆的了,那么这个时候喷漆,组装,已经可以正常开始工作了.
例如
取配件 组装 喷漆
取配件 组装 喷漆
取配件 组装 喷漆...
只有第一次执行的时候,组装需要等待取配件,喷漆等待组装,第一次组装的时候,第二次已经开始了
上面是什么意思那,就是说,组装需要等待,喷漆也要等待.我们可不可以错开,不让他们依赖指向性
比如流水线的代码
mov ax,1 sub ax,2 cmp ax,ax
每次的结果都需要等待上一次的结果,我们可以写成这样
1 mov ax,1 2 3 mov bx,1 4 5 sub ax,2 6 7 mov cx 3 8 9 cmp ax,ax
其中第二行,和倒数第二行都是我随便加的,就是为了不让指令依赖于上次执行,这就是最简单的流水线,不用等待.
在这里可以说下上面的三目运算符的优化了,为什么不光光是优化,以为JMP跳转的时候,CPU的流水线可能正常执行,比如已经知道到组装了,这个时候你来个跳转,那么又要从头开始,而且组装后面的都不执行了,所以不光光是为了优化掉跳转,还有流水线的作用,上面的代码看着很恶心,可是真是的环境就是这样,不是教你怎么去写,而是教你怎么去看,让你明白他为什么这样写.当然流水线的优化还有很多种.这里只是最简单的一个例子
3.乘法指令
MUL (无符号字节乘法)
指令格式: ax = al * r8/m8
ax(16位寄存器)存放 al * r8(八位寄存器)或者 m8(内存中八位的值)
看下inter手册
这里看一下,除法的指令周期很长,最低的70-77,所以也可以优化
这里可以看出 al要放乘数 其余寄存器放乘数
例如 ax = bl * al(他是乘数,你给多少,都是和他相乘的)
汇编例子
mov al,2 (倍数是2倍) mov bl,8 mul bl
此时算出的记过就放在ax中,因为8位*8位的数字不会超过16位的
无符号的字乘法
当我们16位*16位的怎么办,8 *8的结果是放在ax中
16 *16 则放在 DX(数据寄存器)(AX累加寄存器)当中
高16位放到DX当中,低16位放到AX中
其中乘法的 操作数都需要我们自己给,比如 MUL bl, 算出bl的乘法,默认会和al相乘
乘法指令是利用 OF(overflow溢出标志)和CF(进位标志)来判断乘积的高一班是否具有有效数值
有符号的字节乘法
IMUL r8/m8
ax = al * r8/m8 (和上面一样,结果放到ax中,al可以×八位的寄存器,或者内存取出的数值的8位数值
和内存取出来的数值相乘(400的偏移处我给的是11所以最后ax结果是11)
有符号的字乘法
16*16的和无符号的一样
高位放到DX当中,低位放到AX当中
谈到这里我们发现,乘法的指令周期特别长,我们也可以做优化,可以用位运算
对标志位的影响:
会影响OF和CF标志
4.位运算,左移右移
1.左移指令
这个算是附加的,主要是我们要用位运算吧乘法优化掉
SHL 左移 ,高位补零,看下语法
可以是寄存器,内存,不支持立即数,因为立即数哪里都是- ,带有1的则是默认写的时候是左移一位,只能写1
如果给1以上,就要放到CL当中了
寄存器和CL低八位寄存器(注意这里是CL则我们写的时候要注意如果寄存器左移动的时候,则给CL指定个数)
左移,最高位补零
2的二进制
0000 00010 SHL 之后 0000 0100 (4)
8086不支持这样写
SHL ax,3 ;支持这样写 SHL ax,1 默认的是往左移动一位 上面第一句,那些是以后出现的
说道左移,则可以用它来替代掉乘法
例如
mov al,2 mov bl,3 mul al 替换成 mov bl,3 mov cl,2 shl bl,cl
仔细看一下,我们转大了,inter指令周期最起码缩少了10倍,所以说有的时候写一行汇编代码,需要想很长时间,
比如
mov ax,0
你认为是很快了是吗,其实inter指令周期是4,不行的话自己可以查询看一下, reg,imm这一行
但是你写为
xor ax,ax (xor代表异或的意思,相同为假,不同为真,ax和ax肯定各个二进制位相同,此时相同为0,则都变成0了)
和上面的一样,ax都是变为0,而我则赚了一个1个指令周期,其实还有很多这样的汇编代码,都是这样做出来的
所以说学习汇编,把它当做一门艺术来看.
2.右移指令
SHR 逻辑右移,SAR算术右移
两个的不同
SHR 移动的时候,以0来填充
SAR 移动的时候,符号位填充,也就是真正的右移
和左移相反
右移也可以用于正数的除法
但是除法有除法优化的原理,以后讲,这里掌握两个指令即可.
5.除法指令
除法指令也分为有符号除法,和无符号除法
ax / r8,m8的商,放到AL中,余数放到AH中
16位除法
ax /r16,m16, 16位的商放到AX当中(也就是结果放到AX中),余数放到dx中
DIV (无符号字节除法)
指令 DIV r8/m8
或者 DIV r16/m16
6.符号扩展
什么是符号扩展?
符号扩展是指用一个操作数的符号位(也就是最高位)扩展变大,比如8位变为16位,符号扩展不改变数据大小
CBW al符号扩展至AH (字节扩展)
如果al的最高有效位为0,则AH ==00
AL的最高有效位为1,折AH = FFH AL不变
字扩展
CWD AX的符号扩展至DX
AX的最高有效位是0,则DX ==00
如果为1,则DX = FFFFH AX不变