汇编语言笔记(11)标志寄存器

标志寄存器

CPU内部的寄存器中,有一种特殊的寄存器(对于不同的处理机,个数和结构都可能不同)具有以下3种作用。

(1) 用来存储相关指令的某些执行结果;

(2) 用来为CPU执行相关指令提供行为依据;

(3) 用来控制CPU的相关工作方式。

8086CPU中的标志寄存器有16位,其中存储的信息通常被称为程序状态字(PSW),标志寄存器以下简称为flag。标志位如图:

15  14  13   12   11   10   9    8    7    6    5   4    3    2   1   0
                  OF   DF   IF   TF   SF   ZF       AF        PF      CF

flag的1,3,5,12,13,14,15位在8086CPU中没有使用,不具有任何含义。而0,2,4,6,7,8,9,10,11位都具有特殊的含义。

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表示逻辑真,0表示逻辑假,所以,zf = 1结果为0,zf = 0结果不为0.

注意,在8086CPU的指令集中,有的指令的执行是影响标志寄存器的,比如, add, sub, mul, div, inc, or, and等,他们大都是运算指令(进行逻辑或算术运算):有的指令的执行对标志寄存器没有影响,比如,mov, push, pop等,他们大都是传送指令。在使用一条指令的时候,要注意这条指令的全部功能,其中包括,执行结果对标志寄存器的那些标志位造成影响。

PF标志

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

比如,指令:

mov al,1

add al,10

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

mov al, 1

or al, 2

执行后,结果为 0000 0011B,其结果有2(偶数)个1,则pf = 1;

SF标志

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

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

这也就说,CPU在执行add等指令时,时必然要影响到SF标志位的值的。至于我们需不需要这种影响,

那就看我们如何看待指令所进行的运算了。

比如:

mov al,1000 0001B

add al,1

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

mov al,1000 0001B

add al, 0111 1111B

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

CF标志

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

比如:

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记录了向更高位的借位值

当两个数据相加的时候,有可能产生从最高有效位向更高位的进位。比如,两个8位数据:98H+98H,将产生进位。由于这个进位值在8位数中无法保存。其实CPU在运算的时候,并不丢弃这个进值位,而是记录在一个特殊的寄存器的某一位上。8086CPU就用flag的CF位来记录这个进位值。

OF标志

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

这里的溢出只针对有符号数

比如:

mov al, 98

add al, 99

执行后将产生溢出。因为进行有符号数运算是:

(al) = (al) + 99= 99 + 98 = 197 注意:这里是十进制的数。

197超出了机器所能表示的8位有符号数的范围: -128~127

add执行后:CF = 0,OF= 1,即他没有产生进位,但发生了溢出。进位对于无符号数,溢出对于有符号数。

所以对于机器码进行add或者sub等都会影响OF,CF。至于要使用那种影响要看如何看待这段机器码。

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

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

看一下CF的含义。在执行adc指令的时候加上的CF的值的含义,是由adc指令前面的指令决定的,也就是说,关键在于加上的CF值是被什么指令设置的。显然,如果CF的值是被sub指令设置的,那么它的含义就是错位值;如果是被add指令设置的,那么它的含义就是进位值。我们来看一下两个数据:0198H和0183H如何相加的:

01 98

01 83

+

03 1B

可以看出,加法可以分两步进行:1,低位相加;2高位相加再加上低位相加产生的进位值。

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

add al, bl

adc ah,bh

看来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

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

例如:

编程,计算 1E F000 1000H + 20 1000 1EF0H, 结果放在ax(最高16位),bx(次高16位),cx(低位16位)中。

计算分3步进行:

(1)先将低16位相加, 完成后,CF中记录本次相加的进位值;

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

(3)最后高16位和CF(来自次高16位的进位值)相加,完成后,CF中记录本次相加的进位值。

程序如下。

mov ax, 001EH

mov bx, 0F000H

mov cx, 1000H

add cx, 1EF0H

adc bx, 1000H

adc ax 0020H

sbb指令

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

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

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

比如指令 sbb ax, bx 实现的功能是: (ax) = (ax) - (bc) - CF

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

mov bx, 1000H

mov ax, 003EH

sub bx, 2000H

sbb ax, 0020H

sbb和adc是基于同样的思想设计的两条指令,在应用思路上和adc类似。

cmp指令

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

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

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

比如,指令cmp ax,ax,做(ax) - (ax)的运算,结果为0,但并不在ax中保存,仅影响flag的相关各位,指令执行后:zf = 1, pf = 1, sf = 0, cf = 0, of = 0.

下面指令:

mov ax, 8

mov bx,3

执行后:(ax) = 8, zf = 0, pf = 1, sf = 0, cf = 0, of = 0.

其实,我们通过cmp指令执行后,相关标志位的值就可以看出比较的结果。

无符号数比较

cmp ax, bx

如果(ax) = (bx) 则(ax) - (bx) = 0,所以:zf = 1;

如果(ax)(bx) 则(ax) - (bx) = 0,所以:zf = 0;

如果(ax)<(bx) 则(ax) - (bx)将产生借位,所以:zf = 1;

如果(ax)(bx)则 (ax) - (bx)不必借位,所以:cf = 0;

如果(ax)>(bx)则(ax) - (bx)既不必借位,结果又不为0,所以:cf = 0并且zf = 0;

如果(ax)(bx) 则(ax)-(bx)即可能借位,结果可能为0,所以:cf = 1或zf =1.

现在我们可以看出比较指令的设计思路,即:通过做减法运算,影响标志寄存器,标志寄存器的相关位记录了比较结果。

即:cmp ax, bx

执行后:;

zf = 1,说明(ax)=(bx)

zf = 0,说明(ax) (bx)

cf = 1,说明 (ax) <(bx)

cf = 0,说明 (ax) ≥ (bx)

cf = 0 && zf = 0, 说明(ax) > (bx)

cf = 1或 zf = 1, 说明(ax) ≤(bx)

有符号数比较

cmo ah, bh

zf = 1,说明(ah)=(bh)

zf = 0,说明(ah) (bh)

sf = 1&& of = 0 (ah) < (bh)

// of = 0,说明没有溢出,逻辑上真正结果正负=实际结果正负;

sf = 1, 而of = 1 (ah) > (bh)

//of = 1, 说明有溢出,如果因为溢出导致了结果为负,那么逻辑上真正的结果必然为正

sf = 0, of = 1 (ah) < (bh)

//of = 1, 说明有溢出,如果因为溢出导致了实际结果为正,那么逻辑上真正的结果必然为负。

sf = 0, of = 0 (ah)≥(bh)

单纯地考查sf的值不可能知道结果的正负。因为sf记录的是ah中的8位二进制信息所表示的数据的正负。cmp, ah, bh执行后,sf记录的是(ah) - (bh)所得到的8位结果数据的正负。

虽然这个结果没有在我们能够使用的寄存器或内存单元中保存,但是在指令执行的过程中,它暂存在CPU内部的暂存器中。

所得到的相应结果的正负,并不能说明,运算所应该得到的结果的正负。这是因为在运算的过程中可能发生溢出。如果有这种情况发生,那么,sf的值就不能说明任何问题。比如:

mov ah, 22H

mov bh, 0A0H

sub ah, bh

结果 sf = 1,运算实际得到的结果是(ah) = 82H,但是在逻辑上,运算所应该得到的结果是:34 -(-96) = 130. 就是因为130这个结果作为一个有符号数超出了-128~127这个范围,在ah中不能表示,而ah中的结果被CPU当作有符号数解释位-126。而sf被用来记录这个实际结果的正负,所以sf = 1。但是sf = 1不能说明在逻辑上,运算所得到的正确结果的正负。

又比如:

mov ah 08AH

mov bh,070H

cmp ah,bh

结果 sf = 0,运算(ah) - (bh)实际得到的结果是1AH,但是在逻辑上,运算所应该得到的结果是:(-118) - 112 = -230. sf记录实际结果的正负,所以sf = 0.但sf = 0不能说明在逻辑上,运算所得到的正确结果。

所以。我们应该在考察sf(得知实际结果的正负)的同时考查of(得知有没有溢出),就可以得知逻辑上真正结果的正负,同时就可以知道比较的结果。

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

因为cmp指令可以同时进行两种比较,无符号数比较和有符号数比较,所以根据cmp指令的比较结果进行转移的指令也分为两种,即根据无符号数的比较结果进行转移的条件转移指令(他们检测zf,cf的值)和根据有符号数的比较结果进行转移的条件转移指令(他们检测sf,of和zf的值)

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

指令 含义 检测的相关标志位

jcxz cx值为0则转移 cx = 0

je 等于则转移 zf = 1

jne 不等于则转移 zf = 0

jb 低于则转移 cf = 1

jnb 不低于则转移 cf = 0

ja 高于则转移 cf = 0且 zf = 0

jna 不高于则转移 cf = 1或zf = 1

这些指令比较常用,他们都很好记忆,他们的第一个字母都是j,表示jump;后面的字母表示意义如下。

e: 表示equal

ne:表示not equal

b:表示below

nb:表示not below

a:表示above

na:表示not above

比如:

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

data段的8个字节如下:

data segment

db 8, 11, 8, 1, 8, 5, 63,38

data ends

程序如下:

    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

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

DF标志和串传送指令

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的功能是将ds:si指向的内存单元中的字节送入es:di中,然后根据标志寄存器df位的值,将si和di递增或递减

格式:movsw

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

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

rep movsb

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

s:movsb

loop s

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

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

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

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

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

编程,用串传送指令,将data段中的第一个字符串复制到它后面的空间中。
data segment
    db 'Welcome to masm!'
    db  16 dup(0)
data ends
我们分析一下一下,使用串传送指令进行数据的传送,需要给它提供一些必要的信息,它们是:
1 传送的原始位置:ds:si;
2 传送的目的位置:es:di;
3 传送的长度:cx;
4 传送的方向:df;
在这个问题中,这些信息如下。
1 传送的原始位置:data:0;
2 传送的目的位置:data:0010;
3 传送的长度:16;
4 传送的方向:因为正方向传送(每次串传送指令执行后,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 : 0010
mov cx, 16                   ;(cx) = 16,rep循环16次
cld                          ;设置df = 0, 正向传送
rep movsb

pushf 和 popf

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

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

标志寄存器在Debug中的表示

在Debug中,标志寄存器是按照有意义的各个标志位单独表示的。在Debug中,我们可以看到下面的信息。

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

标志 值为1的标记 值为0的标记

of OV NV

sf NG PL

zf ZR NZ

pf PE PO

cf CY NC

df DN UP

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值