汇编语言(十二)标志寄存器
我们先来看上一章的实验十
实验十 编写子程序
一.显示字符串
也就是我们要自己编写子程序显示的部分!
代码如下:
1 | show_str:push cx |
二.解决除法溢出的问题
复习div可以做除法
1 | 进行8位除法的时候,al存储结果的商,ah存储结果的余数。 |
但是如果结果的商大于al或者ax能存储的最大值,那么将如何?
比如下面的程序
1 | mov bh,1 |
显然这是8位除法,商为1000,但是1000在al中放不下。
或者比如
1 | mov ax,1000h |
显然这是16位除法,但是商为11000h,在ax中也放不下。
上述错误都可以称除法溢出
我们采用divdw来解决
功能:进行不会产生溢出的除法运算,被除数为dword型,除数为word型,结果为dword型。
参数:(ax)=dword型数据的低16位。
(dx)=dword型数据的高16位。
(cx)=除数
返回结果:(dx)=结果的高16位,(ax)=结果的低16位
(cx)=余数。
应用举例:计算1000000/10(F4240h/0Ah)
1 | mov ax,4240h |
结果(dx)=0001h,(ax)=86A0h,(cx)=0
我们给出构造divdw的基本公式:
1 | X:被除数 |
这样转化为不会产生溢出的除法!
1 | ax是被除数低位,结果低位 |
我们尝试写一下:
1 | divdw: |
先来看标志寄存器。
第十一章 标志寄存器
有一种特殊的寄存器,具有以下三种作用。
简记为控制CPU相关工作方式。
本章标志寄存器简称flag是我们要学习的最后一个寄存器。
之前学习了13个
1 | ax,bx,cx,dx,si,di,bp,IP,cs,ss,es,ds,sp |
flag寄存器是按位起作用的,每一位都有专门的含义,记录特定的信息。
8086的flag寄存器结构如图所示
这里的
1 | 0,2,4,6,7,8,9,10,11位都具有特殊的含义! |
11.1 ZF标志(见0置1)
flag的6位是ZF,零标志位。它记录执行相关指令之后,其结果是否为0。如果结果位0,那么zf=1;如果结果不
为0,那么zf=0
比如:
1 | mov ax,1 |
执行后,结果为0,则zf=1
1 | mov ax,2 |
执行后结果不为0,zf=0
再看
1 | mov ax,1 |
执行后结果不为0,则zf=0,表示“结果非0”
11.2 PF标志(见偶1置1)
这是奇偶标志位,它记录相关指令执行之后,其结果的所有bit位中1的个数是否为偶数,偶数的话pf=1,奇数的话pf=0.
比如:
1 | mov al,1 |
执行后,结果为00001011b,显然1有奇数个,则pf=0
或者
1 | mov al,1 |
执行后为00000011b,显然偶数个1,pf=1
11.3 SF标志(讲解了补码运算!见负置1)
符号标志位。它记录相关指令执行之后,其结果是否为负。如果结果为负,sf=1;结果为正,sf=0
计算机中通常用补码来表示有符号数据。计算机中的一个数据可以看成有符号数,也可以看成是无符号数。比如:
1 | 00000001B,可以看成无符号数1,或者有符号+1 |
对于同一个二进制数据,计算机可以将它当作无符号数来运算,也可以当作有符号数来运算。比如
1 | mov al,10000001B |
结果,(al)=10000010B
当作无符号的话,add相当于计算129+1=130。当作有符号的话,相当于-127+1=-126。
SF标志就是CPU对有符号数运算结果的一种记录,记录数据的正负。在我们将数据当作有符号数来计算的时候,可以通过它来得知结果的正负。如果当作无符号sf就没有意义。
比如
1 | mov al,10000001B |
当作有符号,执行后,结果为10000010B,即-126,结果为负,则sf=1。
下面看特殊情况:
1 | mov al,10000001B有符号数-127 |
执行后结果为0,按正数来算,sf直接为0。
11.4 CF标志(见进为1)
flag的第0位为CF。一般情况下,在进行无符号数运算的时候,它记录了运算结果的最高有效位向更高位进位的值,或从更高位的错位值
比如
1 | mov al,98h |
现在举个例子看减法错位
1 | mov al,97h |
11.5 OF 标志(有符号数见溢为1)
我们看两个溢出的例子,也就是运算结果超出了机器所能表达的范围
1 | mov al,98 |
执行后将产生溢出,因为al = al + 99=98+99=197,而8位有符号数能表示-128~127。(看来寄存器默认存有符号数)
再来看
1 | mov al,00F0h也就是有符号数-16的补码(因为11110000为负数,计算机计算时取补码为00010000也就是-16,符号由初始数决定) |
flag的11位是OF,溢出标志位。一般情况下OF记录了有符号数运算结果是否发生溢出(这里我们也复习了负数计算时先取补码再计算)
溢出,OF=1,否则OF=0。
注意:CF对无符号数的进位!OF对有符号数的溢出!
(其实就是有符号数叫溢出,无符号数叫进位)
举例:
1 | mov al,0F0h |
补充:计算机有符号运算,正数和负数的加法,就是正数和负数补码的异或运算!
11.6 adc指令
带进位加法指令。比如指令adc ax,bx
实现的功能是(ax) = (ax) + (bx) + CF
例:
1 | mov ax,2 |
显然bx-ax时存在进位所以cf = 1。最后执行adc ax,1
时执行ax + 1 + cf = 4。
例:
1 | mov al,98h |
其实这个指令是用来分解大寄存器加法的,因为ax等寄存器都是16位,16位的加法,如下:
1 | 0198h |
我们可以通过adc来实现,如下:
1 | add al,bl |
显然我们在做完低位加法之后会产生进位cf=1,之后我们高位加法并且要加上进位。
我们来做一个更大的运算
1 | 1ef000h |
我们可以分成两步计算,两位两位做加法,每一步都加上前一步的进位即可!
1 | mov ax,001eh |
再来看
1 | 1ef0001000h |
首先加低16位,记录cf值;再加次高16位,加上cf值同时记录新的cf值;最后加最高16位,加上cf值。
1 | mov ax,001eh |
下面编写一个程序实现两个128位数相加。
128位显然需要8个字单元。由低地址到高地址单元依次存放128位数据由低到高的各个字。结果存储在第一个数的存储空间即可。
用ds:si指向第一个数的存储空间,ds:di指向第二个数的存储空间。
1 | add128:push ax |
这里有个问题,自增语句inc si
用了两次,可不可以直接用add si,2
来代替?其实是可以的,只不过inc si
只占一个字节,用两次才占两个字节。但是add si,2
一次就三个字节,占空间很大。
11.7 sbb指令
带借位减法指令。指令sbb ax,bx
实现的功能是(ax) = (ax) - (bx) - cf
比如计算
1 | 003e1000h |
我们采用如下指令来做
1 | mov bx,1000h |
可以看到sub产生进位cf=1然后再用sbb将cf也算上。
11.8 cmp指令
比较指令,相当于减法,只是不保存结果。执行后对标志寄存器产生影响。
比如cmp ax,ax
做ax-ax
运算结果为0,但不在ax中保存,执行后,zf=1,pf=1,sf=0,cf=0,of=0。
因为zf是判断0的,见0置1,所以为1,其他同理,pf见偶置1确实为1,of见溢出置1,没有溢出所以为0。
比如下面指令
1 | mov ax,8 |
执行后ax=8,但是结果5中有两个1所以pf=1,zf=1,因为非0,of见溢出置1,没有溢出所以of=0。
其实我们也可以通过看标志寄存器的值来看比较的结果!
比如
1 | 如果ax=bx显然ax-bx=0所以zf=1 |
所以我们可以通过标志寄存器来看ax与bx的关系。
当然上面是指无符号运算,我们下面来看有符号运算。(减法用补码表示!负数也用补码表示!)
当ah<bh
可能引起sf=1因为为负。
1 | ah=1 |
不过sf=1并不能说明 操作对象1< 操作对象2
因为
1 | ah=22h,bh=a0h,ah-bh=34-(-96)=82h是-126的补码 |
得到相应结果的正负并不能说明运算所应该得到结果的正负。因为在运算过程中可能存在溢出。
比如
1 | mov ah,22h |
如果没有发生溢出,计算出的结果和数学上真正的结果应该一致了!
所以sf与其无关!
我们考查sf的同时也关注of,看是否存在溢出,即可知道cmp比较的结果了
1 | 1.sf=1而of=0也就是没有溢出!这是最好的情况,逻辑结果和计算结果相等!所以直接判断cmp即可。 |
11.9 检测比较结果的 条件转移指令
比如jcxz通过检测cx来修改ip。注:条件转移指令的转移位移范围[-128,127]
下面是一些根据无符号数比较结果进行转移的指令 和 根据有符号数的比较结果进行转移的条件转移指令。
可以简记below,above等等。带n表示否定,not equal, not below, not above等。
并且检测 等于 只需要zf,检测大于或小于则需要cf。
实现如下功能:
如果ah=bh则ah = ah + ah,否则ah = ah + bh
1 | cmp ah,bh |
这里也就是je去检测zf是否为1,如果为1代表ah=bh直接跳转到s执行。
这里注意,我们是否使用cmp指令要看需不需要,和je的执行无关,因为je只检测zf
1 | mov ax,0 |
因为执行add ax,0使得zf=1所以je直接执行跳转到s。
剩下的其他指令执行原理也类似。
下面来做题:
采用比较的方式!
1 | mov ax,data |
或者也可以这样
1 | mov ax,data |
和上面类似,我们来编程一下
1 | mov ax,data |
或者也用ok的语句,判断大于就跳转到ok
1 | mov ax,data |
11.10 DF标志和串传送指令
df=0,每次操作后si,di递减。
df=1,每次操作后di,si递增。
下面看一个串传送指令
1 | #movsb |
用汇编语言描述如下
1 | mov es:[di], byte ptr ds:[si]//注意实际8086并不支持这个指令,这只是一个描述 |
也就是传送一个字节到es:[di]
,当然也可以传送一个字。然后根据寄存器df的值将si和di自增或者自减2。
1 | mov es:[di], word ptr ds:[si] |
记作movsw
。
一般来说movsb
和movsw
都和rep
配合使用,格式如下:
1 | rep movsb |
因为df决定着为i和di的增还是减,这也叫df是决定方向的。而cpu中对df进行设置的指令如下
1 | cld: df = 0 |
来看一个例子
(1)用串传送指令,将data段中第一个字符串复制到其后面的空间中。
1 | data segment |
我们知道串传送指令默认的方向为:
1 | 传送的起始位置ds:[si] |
在本题中参数如下:
1 | 原始位置 data:0 |
所以可以编写程序如下:
1 | mov ax,data |
(2)用串传送指令,将F000H段中最后16个字符(显然最后一个字符也就是F000:FFFF)复制到data段中。
1 | data segment |
这里我们可以使用逆向传送
1 | 原始位置:F000:FFFF |
程序可以如下:
1 | mov ax,f000h |
11.11 pushf 和popf
pushf是将标志寄存器的值压入栈中,而popf是从栈中弹出数据,送入标志寄存器中。
也就是为简介访问标志寄存器提供了一种方法。
一道例题,执行之后(ax)=?
1 | mov ax,0 |
回忆标志寄存器图
所以这里flag = 0041h出栈到ax。
然后进行与运算两次即可。
11.12 标志寄存器在debug中表示
如图所示,有对应的表示。
下面看之前的一个题目
编写程序,统计F000:0处32个字节中,大小在[32,128]的数据的个数。
1 | mov ax,0f00h |
编写程序,统计32个字节在区间在(32,128)也就是开区间的字节的个数。
1 | mov ax,0f00h |
本章最后的实验十一单独找时间写一篇。