• [汇编语言]标志寄存器



    CPU内部的寄存器中,有一种特殊的寄存器,具有以下3种作用

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

    这种特殊的寄存器在8086CPU中,被称为标志寄存器,8086CPU的标志寄存器有16位,其中存储的信息通常被称为程序状态字(PSW),我们已经使用过8086CPU的ax、bx、cx、dx、si、di等13个寄存器,本章中的标志寄存器(flag)是最后一个寄存器

    flag和其他寄存器不一样,其他寄存器是用来存放数据的,都是整个寄存器具有一个含义,但是flag寄存器是按位来起作用的,也就是说每一位都有专门的含义,记录特定的信息:

    在这里插入图片描述
    flag的1、3、5、12、13、14、15位在8086CPU中没有使用,不具有任何含义,而其他位都具有特殊的含义

    这一章中,我们学习相关标志位以及典型指令

    一、ZF标志

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

    比如指令

    mov ax,1
    sub ax,1
    执行后,结果为0,ZF = 1
    mov ax,2
    sub ax,1
    执行后,结果不为0,ZF = 0
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    二、PF标志

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

    mov al,1
    add al,10
    执行后,结果为00001011B,其中奇数个13个),所以pf = 0
    
    • 1
    • 2
    • 3

    三、SF标志

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

    10000001B可以看作是无符号数129,也可以看作是有符号数-127


    四、CF标志

    flag的第0位是CF,进位标志位。一般情况下,在进行无符号数运算的时候,它记录了运算结果的最高有效位向更高位的进位值或是借位值(减法就是借位)
    在这里插入图片描述

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

    五、OF标志

    如果超出了机器所能表示的范围称为溢出。

    那么什么是机器所能表示的范围呢?

    比如说,指令运算的结果用8位寄存器或是内存单元来存放,比如add al,3,那么对于8位的有符号数据,机器所能表示的范围就是-128 ~ 127,同理,对于16位有符号数据,机器所能表示的范围就是-32768 ~ 32767

    如果运算结果超出了机器所能表达的范围,将产生溢出

    这里所讲到的溢出只是对有符号数运算而言。下面看两个溢出的例子:

    mov al,98
    add al,99
    
    • 1
    • 2

    执行后将产生溢出,因为add al,99的有符号数运算是:(al)=(al)+99 = 99 + 98 = 197,结果197超过了8位有符号的范围

    因为进行的是有符号数运算,所以al中存储的是有符号数,而C5H是由符号数-59的补码,如果我们用add指令进行的是有符号数运算,则99 + 98 = -59这样子的结果是无法接受的,造成这种情况的原因实际上对应的结果是197,为一个由符号数,在8位寄存器al中存放不下。

    由于在进行有符号数运算时,可能会发生溢出而造成严重的结果错误。则CPU需要对指令执行后是否产生溢出进行记录。flag的第11位是OF,溢出标志位,一般情况下,OF记录了有符号数运算的结果是否发生了溢出。如果发生了溢出,则OF = 1,如果没有CF = 0

    CF是对无符号数运算有意义的标志位,而OF是对有符号数运算有意义的标志位。

    比如:

    mov al,98
    add al,99
    
    • 1
    • 2

    add指令执行后,CF = 0,OF = 1,CPU在执行add等指令的时候就包含了两种含义:无符号数和有符号数的运算。对于无符号数运算,CPU用CF位来记录是否产生了进位;对于有符号数来说,用OF来记录是否产生了进位,当然还有CF来记录有结果的符号。对于无符号运算,99+98没有进位,CF=0,对于有符号数的运算,99+98发生了溢出,OF=1


    六、adc指令

    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
    
    • 1
    • 2
    • 3
    • 4

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

    可以看出,adc指令比add指令多加了一个CF位的值。

    为什么CPU要提供这样一条指令?

    在执行adc指令的时候加上CF值的含义是由adc指令前面的指令所决定的,也就是说,关键在于所加上的CF值被什么指令设置的。显然,如果CF的值是被sub指令设置的,那么它的含义加上借位值,如果是被add指令设置的,它的含义就是进位值

    下面的指令和add ax,bx具有相同的结果:

    add al,bl
    adc ah,bh
    
    • 1
    • 2

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

    编程,计算1EF000H+201000H,结果存在ax(高16位)bx(低16)
    因为两个数据的位数都大于16,用add指令无法进行计算。我们将计算分两步进行,先将低16位相加,然后将高16位和进位值相加。

    mov ax,001EH
    mov bx,0F000H
    add bx,1000H
    adc ax,0020H
    
    • 1
    • 2
    • 3
    • 4

    adc指令执行后,可能产生进位值,所以也会对CF位进行设置。由于有这样的功能,我们就可以对任意大的数据进行加法运算

    编程,计算1EF0001000H+2010001EF0H,结果存在ax(最高16位)bx(次高16位)cx(低16位)

    1. 先将低16位进行相加,完成后,CF记录本次相加的进位值
    2. 然后将次高16位和CF相加,完成后CF记录本次进位
    3. 最高16位和CF相加,完成后,CF中记录本次相加的进位值
    mov ax,001EH
    mov bx,0F000H
    mov cx,1000H
    add cx,1EF0H
    adc bx,1000H
    adc ax,0020H
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    七、sbb指令

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

    指令格式:sbb 操作对象1,操作对象2
    功能:操作对象1 = 操作对象1 - 操作对象2 - CF

    比如指令:sub ax,bx实现的是:(ax)=(ax)-(bx)-CF

    sbb指令执行后,要对CF进行设置,利用sbb指令可以对任意大的数据进行减法运算。比如计算003E1000H-00202000H,结果存在ax、bx中

    程序如下:

    mov bx,1000H
    mov ax,003Eh
    sub bx,2000H
    sbb ax,0020H
    
    • 1
    • 2
    • 3
    • 4

    八、cmp指令

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

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

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

    比如:指令cmp ax,ax,做(ax)-(ax)的运算,结果为0,但并不在ax中保存,仅影响flag的相关各位。——比如看借不借位,结果为不为0决定了CF和ZF

    cmp ah,bh
    如果(ah)=(bh),则(ah)-(bh) = 0,所以zf = 1
    如果(ah)≠(bh),则(ah)-(bh) ≠ 0,所以zf = 0

    所以可以根据cmp指令执行后的zf值,就可以判断两个数据是否相等

    对于有符号数,(ah)< (bh)的情况下,(ah)-(bh)可能会引起sf = 1,即结果为负。但是结果为负不一定说明(ah)<(bh),比如(ah)-(bh) = 34 - (-96) = 82H,82H是-126的补码,所以sf = 1


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

    转移指的是他可以修改IP,而条件指的是他可以根据某种条件,决定是否修改IP

    比如,jcxz就是一个条件转移指令,他可以检测cx中的数值,如果(cx)=0,就修改IP,否则什么也不做。所有条件转移位移都是-128 ~ 127

    除了jcxz外,CPU还提供了其他条件转移指令,大多数条件转移指令都检测标志寄存器的相关标志位,根据检测的结果来决定是否修改IP。他们检测的是哪些标志位?他们检测的都是哪些标志位?

    下面是常用的根据无符号数的比较结果进行转移的条件转移指令:

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

    e表示equal
    ne表示not equal
    b表示below
    nb表示not below
    a表示above
    na表示not above

    这些都是cmp指令进行无符号数比较的时候,记录比较结果的标志位,比如je,检测zf位,当zf = 1的时候,进行转移,如果在je前面使用了cmp指令,那么je对zf的检测实际上就是间接的堆cmp比较结果。

    编程实现如下功能:如果(ah)=(bh)则(ah)=(ah)+(ah),否则(ah)=(ah)+(bh)

    cmp ah,bh
    je s
    add ah,bj;否则的情况
    jmp short ok
    s:add ah,ah
    ok:...
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    十、DF标志和串传送指令

    flag的第十位是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,则相应减1

    汇编语言描述movsb功能如下:
    ``mov es:[di],byte ptr ds:[si] ;可以看作底层是这样子写

    如果df = 0
    inc si
    inc di

    如果df = 1
    dec si
    dec di

    可以看出,movsb的功能是将ds:si指向的内存单元中的字节送入es:di,然后根据df的值,将si和di+1或是-1

    当然也可以传送一个字:
    格式:mov sw
    mov es:[di],word ptr ds:[si];可以看作底层是这样子写

    如果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的作用是根据cx,重复执行后面的串传送指令。由于每执行依次movsb指令si和di都会递增或是递减指向后一个单元或是前一个单元。则rep movsb就可以循环实现(cx)个字符的传送

    同理,也可以使用这样子的指令:rep movsw

    相当于是:
    s: movsw
    loop s

    由于flag的df位决定着串传送指令执行后,si和di改变的方向,所以CPU应该提供相应的指令来对df位进行设置,从而使程序员能够决定传送的方向。

    8086CPU提供下面两条指令对df位进行设置:
    cld指令:将标志寄存器的df置为0
    std指令:将标志寄存器的df置为1

    看下面两个程序:

    1. 用串传送指令,将data段中的第一个字符串复制到他后面的空间中
    data segment
    	db 'welcome to masm'
    	db 16 dup(0)
    data ends
    
    • 1
    • 2
    • 3
    • 4

    传送的原始位置:ds:si
    传送的目的位置:es:di
    传送的长度:cx
    传送的方向:df

    mov ax,data
    mov ds,ax
    mov si,0 ;ds:si指向data:0
    mov es,ax
    mov di,16
    mov cx,16
    cld
    rep movsb
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    十一、pushf和popf

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

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

  • 相关阅读:
    把握现在,热爱生活
    django中Models常用的字段及属性介绍
    两个pdf合并成一个pdf?
    Nginx配置使用详解
    ai智能外呼重构企业销售新模式
    Redis
    .netCore用DispatchProxy实现动态代理
    超全Chat GPT论文修改指令
    30 OpenCV 点多边形测试
    一小时入门proteus使用教程
  • 原文地址:https://blog.csdn.net/weixin_51304981/article/details/126739797