第十一章 标志寄存器
8086CPU的标志寄存器有16位,其中存储的信息通常被称为程序状态字(PSW)
我们己经使用过8086CPU的ax、bx、cx、dx、si、di、bp、sp、ip、cs、ss、ds、es等13个寄存器了。
本章中的标志寄存器(以下简称为flag)是我们要学习的最后一个寄存器。
而flag寄存器是按位起作用的,也就是说,它的每一位都有专门的含义,记录特定的信息
11.1 ZF标志
flag的第6位是ZF,零标志位。它记录相关指令执行后,
结果为0 ,ZF = 1
结果不为0,ZF = 0
对于ZF的值,我们可以这样来看,ZF标记相关指令的计算结果是否为0,如果为0,则在ZF要记录下“是0”这样的肯定信息。
在计算机中1 表示逻辑真,表示肯定,所以当结果为0的时候 ZF=1,表示“结果是0 ”。如果结果不为0,则ZF要记录下“不是0”这样的否定信息。
在计算机中0表示逻辑假,表示否定,所以当结果不为0 的时候ZF=0,表示“结果不是0”。
注意:
在8086CPU的指令集中,有的指令的执行是影响标志寄存器的,比如:add、sub、mul、div、inc、or、and等,它们大都是运算指令(进行逻辑或算术运算);
有的指令的执行对标志寄存器没有影响,比如:mov、push、pop等,它们大都是传送指令。
11.2 PF标志
flag的第2位是PF,奇偶标志位。
它记录指令执行后,结果的所有二进制位中1的个数:
为偶数,PF = 1;
为奇数,PF = 0。
11.3 SF标志
flag的第7位是SF,符号标志位。它记录指令执行后,
结果为负,SF = 1;
结果为正,SF = 0。
有符号数与补码
示例
mov al,10000001B
add al,1
结果: (al)=10000010B
SF 标志,就是CPU对有符号数运算结果的一种记录 ,它记录数据的正负。
在我们将数据当作有符号数来运算的时候,可以通过它来得知结果的正负。
如果我们将数据当作无符号数来运算,SF的值则没有意义,虽然相关的指令影响了它的值。
这也就是说,CPU在执行 add 等指令时,是必然要影响到SF标志位的值的。
11.4 CF标志
flag的第0位是CF,进位标志位。
一般情况下,在进行无符号数运算的时候,它记录了运算结果的最高有效位向更高位的进位值,或从更高位的借位值。
比如,两个8 位数据:98H+98H,将产生进位。
由于这个进位值在8位数中无法保存,我们在前面的课程中,就只是简单地说这个进位值丢失了。
其实CPU在运算的时候,并不丢弃这个进位值,而是记录在一个特殊的寄存器的CF位上。
而当两个数据做减法的时候,有可能向更高位借位。
比如,两个 8 位数据:97H-98H,将产生借位,借位后,相当于计算197H-98H。
而flag的CF位也可以用来记录这个借位值。
11.5 OF标志
在进行有符号数运算的时候,如结果超过了机器所能表示的范围称为溢出。
一般情况下,OF记录了有符号数运算的结果是否发生了溢出。
如果发生溢出,OF=1,
如果没有,OF=0。
一定要注意CF和OF的区别:
CF是对无符号数运算有意义的标志位;
而OF是对有符号数运算有意义的标志位。
对于无符号数运算,CPU用CF位来记录是否产生了进位;
对于有符号数运算,CPU 用 OF 位来记录是否产生了溢出,
当然,还要用SF位来记录结果的符号,而CF与OF之间没有任何关系
11.6 abc指令
adc是带进位加法指令 ,它利用了CF位上记录的进位值。
格式: adc 操作对象1,操作对象2
功能: 操作对象1=操作对象1+操作对象2+CF
比如:adc ax,bx 实现的功能是:(ax)=(ax)+(bx)+CF
在执行 adc 指令的时候加上的 CF 的值的含义,由 adc指令前面的指令决定的,也就是说,关键在于所加上的CF值是被什么指令设置的。
显然,如果CF 的值是被sub指令设置的,那么它的含义就是借位值;如果是被add指令设置的,那么它的含义就是进位值。
下面的指令和add ax , bx具有相同的结果:
add al,bl adc ah,bh
看来CPU提供 adc 指令的目的,就是来进行加法的第二步运算的。
adc指令和add指令相配合就可以对更大的数据进行加法运算。
11.7 sbb指令
sbb是带错位减法指令,它利用了CF位上记录的借位值。
格式:sbb 操作对象1,操作对象2
功能:操作对象1=操作对象1–操作对象2–CF
比如:sbb ax,bx
实现功能: (ax) = (ax) – (bx) – CF
sbb指令执行后,将对CF进行设置
11.8 cmp指令
cmp 是比较指令,功能相当于减法指令,只是不保存结果。
cmp 指令执行后,将对标志寄存器产生影响。
其他相关指令通过识别这些被影响的标志寄存器位来得知比较结果。
cmp指令
格式:cmp 操作对象1,操作对象2
功能:计算操作对象1–操作对象2 但并不保存结果,仅仅根据计算结果对标志寄存器进行设置。
只运用cmp与SF指令,在单纯地考察SF 的值不可能知道结果的正负。因为SF 记录的只是可以在计算机中存放的相应位数的结果的正负,此时可能发生溢出
因此,我们应该在考察SF(得知实际结果的正负)的同时考察OF(得知有没有溢出),就可以得知逻辑上真正结果的正负,同时就可以知道比较的结果。
(1)如果SF=1,而OF=0
OF=0,说明没有溢出,逻辑上真正结果的正负=实际结果的正负;
因SF=1,实际结果为负,所以逻辑上真正的结果为负,所以(ah)<(bh)。
(2)如果SF=1,而OF=1
OF=1 ,说明有溢出,逻辑上真正结果的正负≠实际结果的正负;
因 SF=1 ,实际结果为负,实际结果为负,而又有溢出,这说明是由于溢出导致了实际结果为负,
简单分析一下,就可以看出,如果因为溢出导致了实际结果为负,那么逻辑上真正的结果必然为正。这样,SF=1,OF = 1 ,说明了(ah)>(bh)。
(3)如果SF=0,而OF=1
OF=1 ,说明有溢出,逻辑上真正结果的正负≠实际结果的正负;
因SF=0,实际结果非负,而OF=1说明有溢出,则结果非 0 ,所以,实际结果为正。实际结果为正,而又有溢出,这说明是由于溢出导致了实际结果非负,
简单分析一下,就可以看出,如果因为溢出导致了实际结果为正,那么逻辑上真正的结果必然为负。这样,SF=0,OF = 1 ,说明了(ah)<(bh)。
(4)如果SF=0,而OF=0
OF=0,说明没有溢出,逻辑上真正结果的正负=实际结果的正负;
因SF=0,实际结果非负,所以逻辑上真正的结果必然非负。所以(ah)≥(bh)。
11.9 检测比较结果的条件转移指令
“转移”指的是它能够修改IP,而“条件”指的是它可以根据某种条件 ,决定是否修改IP。
比如:jcxz就是一个条件转移指令,它可以检测 cx 中的数值,如果(cx)=0,就修改IP,否则什么也不做。
所有条件转移指令的转移位移都是[-128,127]。
除了 jcxz 之外,CPU还提供了其他条件转移指令,大多数条件转移指令都检测标志寄存器的相关标志位,根据检测的结果来决定是否修改IP。
它们检测的是哪些标志位呢?
就是被cmp指令影响的那些,表示比较结果的标志位。
因为 cmp 指令可以同时进行两种比较,无符号数比较和有符号数比较,所以根据 cmp 指令的比较结果进行转移的指令也分为两种,即:
根据无符号数的比较结果进行转移的条件转移指令,它们检测ZF、CF的值;
根据有符号数的比较结果进行转移的条件转移指令,它们检测 SF、OF和 ZF的值。
指令 | 含义 | 检测的相关标志位 |
---|---|---|
je | 等于则转移 | ZF=1 |
jue | 不等于则转移 | ZF=0 |
jb | 低于则转移 | CF=1 |
jnb | 不低于则转移 | CF=0 |
ja | 高于则转移 | CF=0,ZF=0 |
jna | 不高于则转移 | CF=1或ZF=1 |
编程:统计data段中数值为8的字节的个数,用ax保存统计结果。
编程思路:初始设置(ax)=0,然后用循环依次比较每个字节的值,找到一个和8相等的数就将ax的值加1。
mov ax,data
mov ds,ax
mov bx,0 ;ds:bx指向第一个字节
mov ax,0 ;初始化累加器
mov cx,0
s: cmp byte ptr [bx],8 ;和8进行比较
jne next ;如果不相等转到next,继续循环
inc ax ;如果相等就将计数值加1
next: inc bx
loop s ;程序执行后: (ax)=3
mov ax,data
mov ds,ax
mov bx,0 ;ds:bx指向第一个字节
mov ax,0 ;初始化累加器
mov cx,0
s: cmp byte ptr [bx],8 ;和8进行比较
je ok ;如果相等就转到ok,继续循环
jmp short next ;如果不相等就转到next,继续循环
ok: inc ax ;如果相等就将计数值加1
next: inc bx
loop s
比起第一个程序,它直接的遵循了“等于8则计数值加1”的原则,用je指令检测等于8的情况,但是没有第一个程序精简。
第一个程序用 jne 检测不等于 8 的情况 ,从而间接地检测等于 8 的情况。
11.10 DF标志和串传送指令
flag的第10位是DF,方向标志位。
在串处理指令中,控制每次操作后si,di的增减。
DF = 0:每次操作后si,di递增;
DF = 1:每次操作后si,di递减。
11.10.1 格式1: movsb
功能:(以字节为单位传送)
(1) ((es)×16 + (di)) = ((ds) ×16 + (si))
(2) 如果DF = 0则: (si) = (si) + 1;(di) = (di) + 1
如果DF = 0则:(si) = (si) - 1;(di) = (di) - 1
我们可以用汇编语法描述movsb的功能如下:mov es:[di],byte ptr ds:[si];8086 并不支持这样的指令,这里只是个描述。
如果DF=0:inc si,inc di
如果DF=1:dec si ,dec di
可以看出,movsb 的功能是将 ds:si 指向的内存单元中的字节送入 es:di中,然后根据标志寄存器DF位的值,将 si和di递增或递减。
11.10.2格式2:movsw
功能:(以字为单位传送)
将 ds:si指向的内存字单元中word送入es:di中,然后根据标志寄存器DF位的值,将si和di递增2或递减2。
我们可以用汇编语法描述movsw的功能如下:mov es:[di],word ptr ds:[si];8086 并不支持这样的指令,这里只是个描述。
如果DF=0:add si,2; add di,2
如果DF=1:sub si,2; sub di,2
movsb和movsw进行的是串传送操作中的一个步骤,一般来说,movsb 和 movsw 都和rep配合使用,格式如下:
rep movsb 用汇编语法来描述rep movsb的功能就是: s : movsb loop s
rep movsw用汇编语法来描述rep movsw的功能就是: s : movsw loop s
可见,rep的作用是根据cx的值,重复执行后面的串传送指令。
由于每执行一次movsb指令si和di都会递增或递减指向后一个单元或前个单元,则rep movsb就可以循环实现(cx)个字符的传送
flag的DF位决定着串传送指令执行后,si和di改变的方向
8086CPU提供下而两条指令对DF位进行设置:
cld指令:将标志寄存器的DF位置0
std指令:将标志寄存器的DF位置1
编程:用串传送指令,将data段中的第一个字符串复制到它后面的空间中。
data segment
db ‘Welcome to masm!’
db 16 dup (0)
data ends
我们分析一下,使用串传送指令进行数据的传送,需要给它提供一些必要的信息,它们是:
① 传送的原始位置:ds:si;
② 传送的目的位置:es:di;
③ 传送的长度:cx;
④ 传送的方向:DF。
在这个问题中,这些信息如下:
① 传送的原始位置:data:0;
② 传送的目的位置:data:16;
③ 传送的长度:16;
④ 传送的方向: 因为正向传送(每次串传送指令执行后,si和di 递增)比较方便,所以设置DF=0。
程序:
mov ax,data
mov ds,ax
mov si,0 ;ds:si指向data:0
mov es,ax
mov di,16 ;es:di指向data:16
mov cx ,16 ;(cx)=16,rep循环16次
cld ;设置DF=0,正向传送
rep movsb
11.11 pushf和popf
pushf :将标志寄存器的值压栈;
popf :从栈中弹出数据,送入标志寄存器中。
pushf和popf,为直接访问标志寄存器提供了一种方法。
11.12 标志寄存器在Debug中的表示
在Debug中,标志寄存器是按照有意义的各个标志位单独表示的。
下面列出Debug对我们已知的标志位的表示:
标志 | 值为1的标记 | 值为0的标记 |
---|---|---|
OF | OV | NV |
SF | NG | PL |
ZF | ZR | NZ |
PF | PE | PO |
CF | CY | NC |
DF | DN | UP |