Bootstrap

【汇编语言】标志寄存器(二) —— 标志位驱动的计算:ADC、SBB 和 CMP 的巧妙应用

在这里插入图片描述

前言

📌

汇编语言是很多相关课程(如数据结构、操作系统、微机原理)的重要基础。但仅仅从课程的角度出发就太片面了,其实学习汇编语言可以深入理解计算机底层工作原理,提升代码效率,尤其在嵌入式系统和性能优化方面有重要作用。此外,它在逆向工程和安全领域不可或缺,帮助分析软件运行机制并增强漏洞修复能力。

本专栏的汇编语言学习章节主要是依据王爽老师的《汇编语言》来写的,和书中一样为了使学习的过程容易展开,我们采用以8086CPU为中央处理器的PC机来进行学习。

1. abc 指令

1.1 功能介绍

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

  • 格式:adc 操作对象1,操作对象2

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

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

1.2 举例说明

(1)

mov ax,2 
mov bx,1 
sub bx,ax 		;发生了借位
adc ax,1

执行后,(ax)=4。

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

(2)

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

执行后,(ax)=5。

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

(3)

mov al,98H 
add al,al
adc al,3

执行后,(ax)=34H。

adc执行时,相当于计算: (ax)+3+CF=30H+3+1=34H。

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

1.3 为什么要提供这样的指令?

为什么要加上CF 的值呢?CPU为什么要提供这样一条指令呢?

1.3.1 CF值的含义

先来看一下CF的值的含义。

在执行 adc 指令的时候加上的 CF 的值的含义,由 adc 指令前面的指令决定的,也就是说,关键在于所加上的CF值是被什么指令设置的。

显然,如果CF 的值是被sub指令设置的,那么它的含义就是借位值;如果是被add指令设置的,那么它的含义就是进位值

1.3.2 示例演示

我们来看一下两个数据:0198H和0183H如何相加的:

在这里插入图片描述

可以看出,加法可以分两步来进行:

(1)低位相加;

(2)高位相加再加上低位相加产生的进位值

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

add al,bl
adc ah,bh

1.3.3 得出结论

看来CPU提供 adc 指令的目的,就是来进行加法的第二步运算的。

adc指令和add指令相配合就可以对更大的数据进行加法运算

1.4 例1——对大的数据进行相加

1.4.1 问题描述

编程计算1EF000H+201000H,结果放在ax(高16位)和bx(低16位)中。

1.4.2 问题的分析与解答

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

我们将计算分两步进行,先将低16位相加,然后将高16位和进位值相加。

程序如下。

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

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

1.5 例2——对更大的数据进行相加

1.5.1 问题描述

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

1.5.2 问题的分析与解答

计算分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

1.6 例3——对更大更大的数据进行相加

1.6.1 题目描述

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

  • 名称:add128

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

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

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

1.6.2 问题的分析与解决

程序如下。

add128: push ax
        push cx
        push si
        push di

        sub ax,ax	;将CF设置为0
      
     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       
        
code ends
end start

1.6.3 思考一个问题

inc和loop指令不影响CF位,上面的程序中,能不能将4个inc指令,用

add si,2
add si,2

来取代?

这想都不用想,肯定是不能

如果用add来取代的画,改变了CF记录的进制位,会影响下一次更高位相加的结果。

2. sbb 指令

2.1 功能介绍

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

  • 格式:sbb 操作对象1,操作对象2

  • 功能:操作对象1=操作对象1–操作对象2–CF

  • 比如:sbb ax,bx 实现功能: (ax) = (ax) – (bx) – CF

sbb 指令执行后,将对CF进行设置。

利用 sbb 指令可以对任意大的数据进行减法运算

2.2 举例说明

比如,计算003E1000H-00202000H,结果放在ax,bx中,程序如下:

mov bx,1000H
mov ax,003EH
sub bx,2000H
sub ax,0020H

sbb和adc是基于同样的思想设计的两条指令,在应用思路上和adc类似。在这里,我们就不再进行过多的讨论。

通过学习这两条指令,我们可以进一步领会一下标志寄存器CF位的作用和意义。

3. cmp 指令

3.1 功能介绍

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。

3.2 举例说明

下面的指令:

mov ax,8
mov bx,3
cmp ax,bx

执行后: (ax) = 8,ZF=0,PF=1,SF=0,CF=0,OF=0。

3.3 不同比较的结果

3.3.1 正向来判断标志位

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

例如:cmp ax,bx

在这里插入图片描述

现在我们可以看出比较指令的设计思路

即:通过做减法运算,影响标志寄存器,标志寄存器的相关位记录了比较的结果。

3.3.2 反向来判断两个值的大小

反过来看上面的例子。

指令 cmp ax,bx的逻辑含义是比较 ax和 bx 中的值,如果执行后:

在这里插入图片描述

3.4 cmp 进行有符号数比较

同 add、sub 指令一样,CPU 在执行cmp指令的时候,也包含两种含义:进行无符号数运算和进行有符号数运算

所以利用cmp指令可以对无符号数进行比较,也可以对有符号数进行比较。

上面所讲的是用cmp进行无符号数比较时,相关标志位对比较结果的记录。

下面我们再来看一下如果用cmp来进行有符号数比较时,CPU用哪些标志位对比较结果进行记录。

3.4.1 相等与否的情况

我们以cmp ah,bh为例进行说明:

在这里插入图片描述

所以,我们根据cmp指令执行后ZF的值,就可以知道两个数据是否相等。

3.4.2 探究小于的情况

我们继续看,如果(ah)<(bh)则可能发生什么情况呢?

3.4.2.1 常规情况

对于有符号数运算,在 (ah)<(bh) 情况下,(ah)-(bh)显然可能引起SF=1,即结果为负。

比如:

(ah) = 1,(bh) = 2:则(ah)-(bh)=0FFH,0FFH 为 -1 的补码,因为结果为负,所以SF=1。

(ah)=0FEH,(bx)=0FFH:则(ah)-(bh)=(-2)-(-1)=0FFH,因为结果为负,所以SF=1。

通过上面的例子,我们是不是可以得到这样的结论:cmp 操作对象1,操作对象2 指令执行后,SF=1,就说明操作对象1<操作对象2?

当然不是。

3.4.2.2 溢出时的情况

我们再来看两个例子。

(ah)=22H,(bh)=0A0H:则(ah)-(bh)=34-(-96)=130=82H,82H是-126的补码,所以SF=1。

这里虽然SF=1,但是并不能说明(ah)<(bh),因为显然34>-96。

两个有符号数A 和B 相减,得到的是负数,那么可以肯定A<B,这个思路没有错误;

关键在于我们根据什么来断定得到的是一个负数。CPU将 cmp 指令得到的结果记录在flag的相关标志位中。 我们可以根据指令执行后,相关标志位的值来判断比较的结果。

单纯地考察SF 的值不可能知道结果的正负。因为SF 记录的只是可以在计算机中存放的相应位数的结果的正负。

比如add ah, al执行后,SF记录的是ah中的8位二进制信息所表示的数据的正负。

cmp ah,bh 执行后,sf记录的是(ah)-(bh)所得到的8位结果数据的正负,虽然这个结果没有在我们能够使用的寄存器或内存单元中保存,但是在指令执行的过程中,它暂存在CPU内部的暂存器中。

所得到的相应结果的正负,并不能说明,运算所应该得到的结果的正负。

这是因为在运算的过程中可能发生溢出。如果有这样的情况发生,那么,SF的值就不能说明任何问题。

3.4.2.3 举例说明

比如:

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不能说明在逻辑上,运算所得的正确结果。

但是逻辑上的结果的正负,才是cmp指令所求的真正结果,因为我们就是要靠它得到两个操作对象的比较信息。所以cmp指令所作的比较结果,不是仅仅靠sf就能记录的,因为它只能记录实际结果的正负。

我们考虑一下,两种结果之间的关系,实际结果的正负,和逻辑上真正结果的正负,它们之间有多大的距离呢?从上面的分析中,我们知道,实际结果的正负,之所以不能说明逻辑上真正结果的正负,关键的原因在于发生了溢出。

如果没有溢出发生的话,那么实际结果的正负和逻辑上真正结果的正负就一致了。

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

3.5 举例与总结各种结果的判断

下面,我们以cmp ah,bh为例,总结一下CPU执行cmp指令后,SF和OF的值是如何来说明比较的结果的。

(1)如果SF=1,而OF=0

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

因SF=1,实际结果为负,所以逻辑上真正的结果为负,所以(ah)<(bh)。

(2)如果SF=1,而OF=1

OF=1,说明有溢出,逻辑上真正结果的正负≠实际结果的正负;

简单分析一下,就可以看出,如果因为溢出导致了实际结果为负,那么逻辑上真正的结果必然为正。这样,SF=1,OF = 1 ,说明了(ah)>(bh)。

(3)如果SF=0,而OF=1

OF=1 ,说明有溢出,逻辑上真正结果的正负≠实际结果的正负;

简单分析一下,就可以看出,如果因为溢出导致了实际结果为正,那么逻辑上真正的结果必然为负。这样,SF=0,OF = 1 ,说明了(ah)<(bh)。

(4)如果SF=0,而OF=0

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

因SF=0,实际结果非负,所以逻辑上真正的结果必然非负。所以(ah)≥(bh)。




上面,我们深入讨论了cmp指令在进行有符号数和无符号数比较时,对flag 相关标志位的影响,和CPU如何通过相关的标志位来表示比较的结果。在学习中,要注意领会8086CPU这种工作机制的设计思想。实际上,这种设计思想对于各种处理机来说是普遍的。

结语

今天的分享到这里就结束啦!如果觉得文章还不错的话,可以三连支持一下。

也可以点点关注,避免以后找不到我哦!

Crossoads主页还有很多有趣的文章,欢迎小伙伴们前去点评,您的支持就是作者前进的动力!

在这里插入图片描述

;