• [从零学习汇编语言] - 标志寄存器



    前言

    点赞再看,养成习惯!

    今天我们来讲一下常见CPU组成中的最后一个寄存器:标志寄存器.


    一、标志寄存器的简介

    CPU内部的寄存器中,有一种特殊的寄存器(对于不同的CPU可能其构造和数量均不同),其主要有三中作用:

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

    这种寄存器在我们8086CPU中被称为标志寄存器。8086CPU的标志寄存器有16位,其中存储的信息通常被称为程序状态字(PSW),与其他寄存器不同的是,其他寄存器都是整个寄存器用来存放特定具数据,而标志寄存器则是按照每一位来记录特定信息。比如8086CPU的结构图如下:

    在这里插入图片描述
    flag寄存器中的1,3,5,12,13,14,15位在8086CPU中并没有被应用,不具备任何含义。而剩余各位则都具有其特殊含义,接下来我们就要详细的讲一下剩下的其他各位。

    二、标志位详解

    2.1 ZF标志

    ZF标志位是flag寄存器的第六位,我们可以将其理解为零标志位。它主要是用来记录相关指令执行后,其结果是否为0,如果结果为0则 zf = 1 否则为0。比如我们可以执行如下程序:

    assume cs:code
    code segment 
    		main:
    		     mov ax,1
    			 sub ax,1
    code ends 
    end main 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    运行结果:
    在这里插入图片描述
    运行结果中的nz代表当前结果值非0,zr代表当前结果值为0。

    2.2 PF标志

    奇偶标志位,它负责记录相关指令执行后,其结果的所有bit位中1的个数是否为偶数,如果1为偶数则pf=1 反之为0。

    assume cs:code
    code segment 
         main :  mov al,2
    			 add al,10
    code ends 
    end main
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    运行结果:
    在这里插入图片描述
    我们通过运行结果对比可以知晓:PO代表基数,PE代表偶数

    2.3 SF标志

    flag寄存器的第七位是符号标志位,它用来标志程序的运行结果是否为负,若为负则sf=1,反之为0。

    assume cs:code
    code segment 
         main :  mov al,1
    			 sub al,2
    code ends 
    end main
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    运行结果:
    在这里插入图片描述
    我们通过运行结果对比可以知晓:NC代表整数,CY代表负数

    2.4 CF标志

    在学习CF标志之前,我们要先搞清楚一个基础概念:有符号运算无符号运算

    2.4.1 无符号运算

    什么是无符号运算?无符号运算更像是数的绝对值,比如我们的二进制串1111 1111在无符号运算的时候其表达的含义就是十进制的255,此时用来参与运算的1111 1111是不会考虑正负的。

    2.4.2 有符号运算

    但是同样是1111 1111如果我们想要其表达出正负,按照约定我们就需要通过二进制串的第一位来表示数的正负,正数为0,负数为1。同时为了通过加法代替减法(比如 1 + (-1) 等价于1 - 1),负数在表达时会采用其真码补码来进行表示。比如我们说数字-1,其真码就是1000 0001,其反码就是1111 1110,进一步我们能推出其补码就是1111 1111 。是不是很熟悉,没错这就是我们无符号运算中的255


    我们再回到CF标志位,CF标志位处于flag寄存器的第0位,其主要是用来标识无符号计算时运算结果向更高位进位(或借位)的状态。比如8位寄存器中我们进行一个简单的加法:十六进制的 80 + 7F,对应的程序源码就是:

    assume cs:code
    code segment 
         main :  mov al,80H
    			 mov bl,7FH
                 add al,bl
    code ends 
    end main
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    运行结果:
    在这里插入图片描述

    那么如果是无符号运算的80+80呢?我们再来试一下:

    assume cs:code
    code segment 
         main :  mov al,80H
    			 mov bl,80H
                 add al,bl
    code ends 
    end main
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    运行结果:
    在这里插入图片描述
    我们预期的结果应该是十六进制的100,但是此时由于AL寄存器只能显示八位数字,因此只能显示出低位的00,而消失不见的1此时其实就由CF标志位进行标注。

    2.5 OF标志

    OF标志位处于flag寄存器的第11位,它的作用是记录有符号运算时的溢出情况,比如我们一个8位寄存器可以表达十进制有效范围为:+127 到 -128 ,那如果我们进行的运算超过了这个范围怎么办?这种情况我们就称之为发生了模溢出,此时我们就需要使用OF寄存器进行记录。
    我们简单的通过计算十进制的127+1(即十六进制的7F+1)来模拟一下溢出场景。

    assume cs:code
    code segment 
         main :  mov al,7fH
    			 mov bl,1H
                 add al,bl
    code ends 
    end main
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    运行结果:
    在这里插入图片描述
    此时运算结果为二进制的1000 0000即十进制的-128。很明显不符合我们的预期,这就是由于值溢出导致的。

    2.5.1 CF标志及OF标志的区别

    很多小伙伴 其实并不能很好的区分CF标志位的进位和OF标志位的溢出的概念。首先如果我们使用的是8位寄存器,那么其结构应该如下图所示:
    在这里插入图片描述
    我们以数据1111 1111为例,如果是无符号运算,此时数据结构如下:
    在这里插入图片描述
    此时八位数据全部参与运算,其10进制值为255,如果此时我们将该数据+1,则结果应是:
    在这里插入图片描述
    此时结果值为十进制的256,而由于索引为8的内存单元已经超过了寄存器的长度限制,因此需要借位,因此CF标志需要更改为1,表示发生了借位。


    我们再来看下如果此时进行的是符号运算,则同样是元素1111 1111,由于其首位为符号位1表示负数,因此其表现形式为补码,其真值则为-1
    在这里插入图片描述

    如果此时我们将值+1则其结果为:在这里插入图片描述
    此时由于第七位就是我们的符号位,因此溢出的第八位我们无需关注,因此此时数据0000 0000 表示的值便是十进制的0,此时还并没有发生值的溢出。如果我们此时将数据更改为0111 1111,其符号位为正数,则其真值为十进制的+127

    在这里插入图片描述
    此时值+1则为1000 0000,符号位发生了改变,其真值为十进制的- 128 ,明显不符合我们127 + 1 = 128,发生了值溢出
    在这里插入图片描述

    此时按照规定,OF符号位的值应为1

    2.6 DF标志

    DF标志是Flag寄存器的第十位,被称为方向标志位,其作用就是我们在进行串操作的时候进行SI,DI的递减,如果df=0 则每次操作后si,递增;如果df=1,每次操作后si,di递减。其具体应用我们会在稍后通过新的指令进行讲解。

    2.7 DOSBOX中的flag标志位表示

    在这里插入图片描述

    三、Flag标志位的衍生指令

    3.1 adc指令

    adc 指令是带进位加法指令,它就是利用了CF标志位的进位功能。


    指令格式: adc 对象1,对象2
    如: adc ax,bx
    作用: 对象1 = 对象1+对象2 +cf
    如:adc ax,bx -> ax = ax + bx +cf
    我们来简单的写个程序:

    assume cs:code
    code segment 
    
    	main : mov ax,1
    		   mov bx,2
    		   sub ax,bx ; 此时ax = ffff 发生了借位,cf = 1
    		   adc ax,bx ; ax = ffff + 2 + 1 = 2    此时发生进位,cf=1
    
    code ends 
    end main 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    3.2 sbb指令

    sbb 指令是带进位(借位)信息的减法指令,它与adc指令类似,也是利用CF的值进行计算。


    指令格式: sbb 对象1,对象2
    如: sbb ax,bx
    作用: 对象1 = 对象1 - 对象2 - cf
    如:sbb ax,bx -> ax = ax - bx - cf
    我们来简单的写个程序:

    assume cs:code
    code segment 
    
    	main :  mov  ax ,2 
                mov bx ,1 
    			sbb bx,ax  ; bx = ffff 此时发生借位 cf = 1
    			sbb ax,ax ; ax = ax - ax - 1 = ffff
    
    code ends 
    end main 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    3.3 cmp指令

    cmp是比较指令,其功能相当于减法指令,只不过减法的值并不会被保存到寄存器中。当cmp指令执行后,其比较结果会影响flag寄存器,这样我们就可以通过flag寄存器获知比较的结果(主要影响CF,ZF,OF,AF,PF)。那我们该如何判断比较大小的结果呢?
    比如:

    mov ax,1
    mov bx,1
    cmp ax,bx
    
    • 1
    • 2
    • 3

    3.3.1 相等

    由于cmp ax,bx = ax - bx = 0
    因此 zf = 1 说明 ax 等于bx

    3.3.2 无符号比较

    比如 cmp ax,bx
    若CF = 1 由于cmp进行的是减法运算,因此表示此时运算发生了借位,因此ax 小于 bx 反之若CF= 0 则ax >= bx(需要判断zf的值来判断是否相等)

    3.3.3 有符号比较

    cmp ax,bx
    若 SF = 0 OF = 0 则表示无负数,无溢出,则代表ax > bx
    若 SF = 1 OF = 0 则表示有负数,无溢出 则 ax < bx
    若 SF = 0 OF = 1 则表示无负数 有溢出 则 ax<bx
    若 SF = 1 OF = 1 则表示有负数 有溢出 则 ax > bx

    3.4 状态位条件转移指令

    我们之前有使用过jcxz 转移指令,其通过判断cx寄存器的值来判断是否需要转移,而汇编语言中还存在一些基于Flag寄存器状态位来判断是否转移的指令。

    指令含义相关状态位的值
    je等于则转移zf = 1
    jne不等于则转移zf = 0
    jb低于则转移cf = 1
    jnb不低于则转移cf = 0
    ja高于则转移cf = 0 或 zf=0
    jna不高于则转移cf =1 或 zf =1

    其使用方式与jcxz指令无异,比如我们要判断如果 ax = bx 则cx = 1 反之则为 2

    assume cs:code
    code segment 
    	main :   mov ax,2
    		     mov bx,1
    		     mov cx,0 
    		     cmp ax,bx
    		     je equals
    		     mov cx,2
      equals :   mov cx,1
    
    code ends 
    end main 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    3.5 数据传送指令

    数据传送指令有很多种,比如movsb,movsw等,它们的使用场景大体相同,我们以movsb举例:

    movsb 是一个串传送指令,起作用相当于:

    1. es16 + di = ds16 + si
    2. 如果df = 0 则 di+1 , si+1
    3. 如果df=1 则 di-1 ,si-1

    我们用简单易懂的方式翻译一下:当我们调用movsb的时候,我们会将ds:si的数据复制到es:di 。同理的movsw表示为两个字长,因此di及si的递增递减应为2。

    这里再提一下rep指令,起作用与loop类似,是根据cx寄存器的值重复执行其后续的串转移指令。

    3.6 pushf和popf

    pushf的含义就是push flag,它的作用就是将标志寄存器的值压栈,与之相对的就是popf,其作用就是将标志寄存器的值弹栈。两个指令配合使用是为了可以获取当前flag寄存器的值以作判断。比如:

    assume cs:code
    code segment
    
    	main: 
    	      mov ax,2
    		  mov bx,1
    		  sub ax,bx
    		  pushf
    		  pop ax
    code ends
    
    end main
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    结语

    今天的内容就到此结束了,有疑问的小伙伴欢迎评论区留言或者私信博主,博主会在第一时间为你解答。

    **码字不易,感到有收获的小伙伴记得要关注博主一键三连,不要当白嫖怪哦~
    如果大家有什么意见和建议请评论区留言或私聊博主,博主会第一时间反馈的哦

  • 相关阅读:
    Python 0基础_变现_38岁_day 15(匿名函数)
    搜索技术【广度优先搜索】 - 简介 & 分支限界法
    Nginx介绍与安装
    Spring 事务的种类 ? 传播机制 ?
    Java调用ApacheOpenOffice将Word转PDF
    生产数据自动化同步到预生产
    TCP四次挥手会经历这么多状态
    一文1800字解读性能指标与性能分析
    [开源预告]开源用两千元做的水下机器人方案
    Python的内存优化
  • 原文地址:https://blog.csdn.net/xiaoai1994/article/details/125258881