汇编中的标志寄存器

1.写在前面

之前我的博客已经介绍了一些常用的寄存器,今天我打算介绍一下比较特殊的寄存器,标志寄存器,主要是用来的做一些比较的功能的,同时也会讲一下中断的一些知识。废话不多说,直接上博客吧。

2.本篇博客的概述

在这里插入图片描述

3.标志寄存器

特殊的寄存器主要是下面的3种作用:

  1. 用来存储相关指令的某些执行结果;
  2. 用来为CPU执行相关指令提供行为依据
  3. 用来控制CPU的相关工作方式。

8986CPU的标志寄存器有16位,其中存储的信息通常被称为程序状态字(PSW)。这个特殊的寄存器(flag)的结构图如下:

在这里插入图片描述

3.1ZF标志

flag的第6位是ZF,零标志位。它记录相关指令执行后,其结果是否为0.如果结果为0,那么ZF=1;如果结果不为0,那么ZF=0。

比如,指令:

mov ax,1
and ax,0

执行后,结果为0,则zf=1,表示“结果是0”。

mov ax,1
or ax,0

执行后,结果不为0,则zf=0,表示“结果非0”。

对于zf的值,我们可以这样来看,zf标记相关指令的计算结果是否为0,如果为0,则zf要记录下“是0”这样的肯定信息。在计算机中1表示逻辑真,表示肯定,所以当结果为0的时候,zf=1,表示结果为0,如果结果不为0,则zf要记录下“不是0”这样的否定信息。在计算机中0表示逻辑假,表示否定,所以当结果不为0的时候zf=0,表示结果不是0。

注意,影响标志寄存器的指令:add、sub、div、inc、or、and等。不影响标志寄存器的指令:mov、push、pop。

3.2PF标志

flag的第2位是PF,奇偶标志位。它记录相关指令执行后,其结果的所有bit位中1的个数是否为偶数,如果1的个数为偶数,pf=1,如果为奇数,那么pf=0。

比如,指令:

mov al,1
or al,10

执行后,结果为00001011B,其中有3(奇数)个1,则pf=0;

mov al,1
or al,2

执行后,结果为00000011B,其中有2(奇数)个1,则pf=0;

sub al,al

执行后,结果为00000000B,其中有0(偶数)个1,则pf=1。

3.3SF标志

flag的第7位是SF,符合标志位。它记录相关指令执行后,其结果是否为负。如果结果为负,sf=1;如果非负,sf=0。

注意:计算机中正数最高位是符号位原码反码补码是一样的。负数的最高位是符号位,反码在原来的基础上除了符号位,其他的全部取反,补码在反码的基础上加上1。

SF标志,就是CPU对有符号数运算结果的一种记录,它记录数据的正负。在我们将数据当作有符合数来运算的时候,可以通过它来得知结果的正负。如果我们将数据当作无符号数来运算,SF的值则没有意义,虽然相关的指令影响了它的值。

这也就是说,CPU在执行add等指令时,是必然要影响到SF标志位的值的。至于我们需不需要这种影响,那就看我们的如何看待指令所进行的运算了。

比如:

mov al,10000001B
add al,1

执行后,结果为10000010B,SF=1,表示:如果指令进行的是有符合数的运算,那么结果为负;

mov al,10000001B
add al,01111111B

执行后,结果为0,SF=0,表示:如果指令进行的是有符合数运算,那么结果为非负。

3.4CF标志

flag的第0位是CF,进位标志位。一般情况下,在进行无符号运算的时候,它记录了运算结果的最高有效位向更高位的进位置,或从更高位借位值。

对于位数为N的无符号的数来说,其对应的二进制信息的最高位,即第N-1位,就是它的最高有效位,而假象存在的第N位,就是相对于最高有效位的更高位。

比如:

mov al,98H
add al,al;执行后:(al)=30H,CF=1,CF记录了从最高有效位向更高位的进位值
add al,al;执行后:(al)=60H,CF=0,CF记录了从最高有效位向更高位的进位值

再比如减法:

mov al,97H
sub al,98H;执行后:(al)=FFH,CF=1,CF记录了向更高位的借位值
sub al,al;执行后:(al)=0,CF=0,CF记录了向更高位的借位值

3.5OF标志

在进行有符号数的运算的时候,如经过超过了机器所能表示的范围称为溢出。注意这儿讲的溢出,知识针对有符号的运算而言。

mov al,98
add al,99

执行后将产生溢出,计算的结果是(al)=OC5H,因为进行的是有符号的运算,所以al中存储的是有符号数,而C5H是有符号数-59的补码,如果我们用add指令进行的是有符号运算,则98+99=-59 这样的结果让人无法接受。造成这种情况的原因,就是实际的结果197,作为一个有符号数,在8位寄存器al中存放不下。

mov al,0f0h ;f0h,为有符号数-16的补码
add al,088h ;88h,为有符号数-120的补码

add指令运算的结果是(al)=78H,因为进行的是有符号数运算,所以al中存储的是有符号数,而78H表示有符号数120。如果我们用add指令进行的是有符号数运算,则-16-120=120这样的结果显然不正确。造成这样的情况的原因,就是实际的结果-136,作为一个有符号数,在8位寄存器al中存放不下。

flag的第11位是OF,溢出标志位。一般情况下,OF记录了有符号数运算的结果是否发生了溢出。如果发生了溢出,OF=1,如果没有,OF=0

这儿需要注意的CF和OF的区别:CF是对无符号数运算有意义的标志位,而OF是对有符号数运算有意义的标志位。

CF和OF所表示的进位和溢出,是分别对无符号数和有符号数运算而言的,它们之间没有任何关系。

3.6adc指令

adc是带进位加法指令,它利用了CF上记录的进位值。

指令格式:adc 操作对象1,操作对象2

功能:操作对象1=操作对象1+操作对象2+CF

比如指令 adc ax,bx 实现的功能是:(ax)=(ax)+(bx)+CF

例如:

mov ax,2
mov bx,1
sub bx,ax
adc ax,1

执行后,(ax)=4。adc执行时,相当于计算:(ax)+1+CF=2+1+1=4

mov ax,1
add ax,ax
adc ax,3

执行后,(ax)=5。adc执行时,相当于计算:(ax)+3+CF=2+3+0=5

为什么要加上CF的值呢?CPU为什么要提供这样的一条指令呢?我们都知道加法可以分两步来进行:先是低位相加,高位相加再加上低位相加产生的进位值。于是我们可以得出下面的指令和add ax,bx是具有相同的结果:

add al,bl
adc ah,bh

看来CPU提供adc指令的目的,就是来进行加法的第二步运算。adc指令和add指令相配合就可以对更大的数据进行加法运算。

编程,计算1EF0001000H+2010001EF0H,结果放在ax(最高16位)和bx(次高16位),CX(低16位)中。

因为两个数据的位数都都大于16,用add指令无法进行计算。程序如下:

mov ax,001EH
mov bx,0F000H
mov cx,1000H
add cx,1EF0H
adc bx,1000H
adc ax,0020H

上面的代码计算大概分为3步:

  1. 先将低16位相加,完成后,CF中记录本次相加的进位值;
  2. 再将次高16位和CF(来自低16位的进位值)相加,完成后,CF中记录本次相加的进位值。
  3. 最后高16位和CF(来自次高16位的进位值)相加,完成后,CF中记录本次相加的进位值。

下面编写一个子程序,对两个128位数据进行相加。

名称:add128

功能:两个128位数据进行相加

参数:ds:si指向存储第一个数的内存空间,因数据为128位,所以需要8个字单元,由低地址单元到高地址单元依次存放128位数据从低到高的各个字。运算结果存储在第一个数的存储空间中。

ds:di指向存储第二个数的内存空间。

程序如下:

add128:push ax
			 push cx
			 push si
			 push di 
			 
			 sub ax,ax ;将CF设置为0
			 
			 mov cx,8
		s: mov ax,[si]
			 adc ax,[di]
			 mov [si],ax
			 inc si
			 inc si
			 inc di
			 inc di
			 loop s
			 
			 pop di
			 pop si
			 pop cx
			 pop ax
			 ret

3.7sbb指令

sbb是带借位减法指令,它利用了CF位上记录的借位值。

指令格式:sbb操作对象1,操作对象2

功能:操作对象1=操作对象1-操作对象2-CF

和adc的指令差不多我这儿就不错赘述了。

3.8cmp指令

cmp是比较指令,cmp的功能相当于减法指令,只是不保存结果。cmp指令执行后,将对标志寄存器产生影响,其他相关的指令通过识别这些被影响的标志寄存器位来得知比较结果。

cmp指令格式:cmp操作对象1,操作对象2

功能:计算操作对象1-操作对象2 但并不保存结果,仅仅根据计算结果对标志寄存器进行设置。

结论:

进行无符号数cmp的指令:

  1. 如果(ax)=(bx),则(ax)-(bx)=0,所以zf=1;
  2. 如果(ax)!=(bx),则(ax)-(bx)=0,所以zf=0;
  3. 如果(ax)<(bx),则(ax)-(bx)将产生借位,所以cf=1;
  4. 如果(ax)>=(bx),则(ax)-(bx)不必借位,所以cf=0;
  5. 如果(ax)>(bx),则(ax)-(bx)不必借位,结果又不为0,所以cf=0并且zf=0;
  6. 如果(ax)<=(bx),则(ax)-(bx)既可能借位,结果可能为0,所以cf=1或zf=1;

进行有符号数cmp的指令,以cmp ah,bh为例:

  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)

3.9检测比较结果的条件转移指令

前面我们学了一个条件转移指令:jcxz,它可以检测cx中的数值,如果(cx)=0,就修改IP,否则什么也不做。所以条件转移指令的转移位移都是[-128,127]

无符号数的比较结果进行转移的条件转移指令。

指令含义检测的相关标志位
je等于则转移zf=1
jne不等于则转移zf=0
jb低于则转移cf=1
jnb不低于则转移cf=0
ja高于则转移cf=0且zf=0
jna不高于则转移cf=1或zf=1

我们来看下下面的一组程序。

data段中的8个字节如下:

data segment
	db 8,11,8,1,8,5,63,38
data ends

编程,统计data段中数值为8的字节的个数,用ax保存统计结果。

编程思路:初始设置(ax)=0,然后用循环依次比较每个字节的值,找到一个和8相等的数就将ax的值加1。程序如下:

mov ax,data
mov ds,ax
mov bx,0 ;ds:bx指向第一个字节
mov ax,0 ;初始化累加器
mov cx,8
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,8
s: cmp byte ptr [bx],8;和8进行比较
je ok ;如果相等转到ok
jmp short next ;如果不相等就转next,继续循环
ok:inc ax ;如果相等就将计数值加1
next:inc bx
loop s;程序执行后:(ax)=3

比起第一个程序,它直接遵循了等于8则计数值加1的原则,用je指令检测等于8的情况,但是没有第一个程序精简。第一个程序用jne检测不等于8的情况,从而间接地检测等于8的情况。要注意在使用cmp和条件转移指令时的这种编程思想。

编程,统计data段中数值大于8的字节的个数,用ax保存统计结果

编程思路:初始设置(ax)=0,然后用循环一次比较每个字节的值,找到一个大于8的就将ax的值加1,程序如下:

mov ax,data
mov ds,ax
mov bx,0 ;ds:bx指向第一个字节
mov ax,0 ;初始化累加器
mov cx,8
s: cmp byte ptr [bx],8;和8进行比较
jna next ;如果不大于8转到next,继续循环
inc ax ;如果大于8就将计数值加1
next:inc bx
loop s;程序执行后:(ax)=3

编程,统计data段中数值小于8的字节的个数,用ax保存统计结果

编程思路:初始设置(ax)=0,然后用循环一次比较每个字节的值,找到一个小于8的就将ax的值加1,程序如下:

mov ax,data
mov ds,ax
mov bx,0 ;ds:bx指向第一个字节
mov ax,0 ;初始化累加器
mov cx,8
s: cmp byte ptr [bx],8;和8进行比较
jnb next ;如果不小于于8转到next,继续循环
inc ax ;如果小于8就将计数值加1
next:inc bx
loop s;程序执行后:(ax)=2

3.10DF标志和串送指令

flag的第10位是DF,方向标志位。在串处理指令中,控制每次操作后si、di的增减。

df=0 每次操作后si di 递增

df=1 每次操作后si di 递减

我们来看下面的一个串送指令。

格式:movsb

功能:执行movsb指令相当于进行下面的几步操作。

  1. ((es)*16+(di))=((ds)*16+(si))

  2. 如果df=0则(si)=(si)+1,(di)=(di)+1

    如果df=1则(si)=(si)-1,(di)=(di)-1

用汇编语法描述movsb的功能如下:

mov es:[di],byte ptr ds:[si];8086并不支持这样的指令,这里只是个描述

可以看出,movsb的功能是ds:si指向的内存单元中的字节送入es:di中,然后根据标志寄存器df位的值,将si和di递增或递减。

格式:movsw

movsw的功能是将ds:si指向的内存字单元中的字送入es:di中,然后根据标志寄存器df位的值,将si和di递增2或递减2

用汇编语法描述movsw的功能如下:

mov es:[di],word ptr ds:[si];8086并不支持这样的指令,这里只是个描述

movsb和movsw进行的是串送操作中的一个步骤,一般来说,movsb和movsw都是和rep配合使用,格式如下:

rep movsb
用汇编语法来描述rep movsb的功能就是
s:movsb
loop s

可见,rep的作用是根据cx的值,重复执行后面的串送指令。由于每执行一次movsb指令si和di都会递增或递减指向后一个单元或前一个单元,则rep movsb就可以循环实现(cx)个字符的传送。同样rep movsw指令也是有一样的效果。

8086CPU提供下面两条指令对df位进行设置。

cld指令:将标志位寄存器的df位置0

std指令:将标志位寄存器的df位置1

我们来看下面的两个程序:

  1. 编程,用串传送指令,将data段中的第一个字符串复制到它后面的空间中

    data segment
    	db 'Welcome to masm!'
    	db 16 dup(0)
    data ends
    

    我们分析一下,使用串传送指令进行数据的传送,需要给它提供一些必要的信息,它们是:

    传送的原始位置:ds:si

    传送的目的地址:es:di

    传送的长度:cx

    传送的方向:df

    在这个问题中,这些信息如下:

    传送的原始位置:data:0

    传送的目的位置:data:0010

    传送的长度: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:0100
    mov cx,16 ;(cx)=16,rep循环16次
    cld ;设置df=0 正向传递
    rep movsb
    
  2. 编程,用串串烧指令,将F000H段中的最后16位字符复制到data段中。

    data segment
    	db 16 dup(0)
    data ends
    

    要传送的字符串位于F000H段的最后16个单元中,那么它的最后一个字符的位置:F000:FFFF,是显而易见的。可以将ds:si指向F000H段的最后一个单元,将es:di指向data段中的最后一个单元,然后逆向(即从高位置向低地址)传送16个字节即可。

    传送的原始位置:F000:FFFF

    传送的目的位置:data:000F

    传送的长度:16

    传送的方向:因为逆向传送(每次串传送指令执行后,si和di递减)比较方便,所以设置df=1。

    程序如下:

    mov ax,0f000h
    mov ds,ax
    mov si,0ffffh ;ds:si指向f000:ffff
    mov ax,data
    mov es,ax
    mov di,15 ;es:di指向data:000F
    mov cx,16 ;(CX)=16,rep循环16次
    std ;设置df=1,逆向传送
    rep movsb
    

3.11pushf和popf

pushf 的功能是将标志寄存器的值压栈,而popf是从栈中弹出数据,送入标志寄存器中。

pushf 和popf,为直接访问标志寄存器提供了一种方法。

3.12标志寄存器在Debug中表示

在这里插入图片描述

其中NV是OF标志位,UP是DF标志位,PL是SF标志位,NZ是ZF标志位,PO是PF标志位,NC是CF标志位。

下面列出DeBug对我们的已知的标志位的表示

标志值为1的标记值为0的标记
ofOVNV
sfNGPL
zfZRNZ
pfPEPO
cfCYNC
dfDNUP

4.写在最后

由于博客的篇幅的问题,我这篇的博客,没有介绍中断,下面博客中将介绍中断。

  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值