啥都不会 发表于 2022-5-8 10:42

8086汇编-算数运算指令

本帖最后由 啥都不会 于 2022-5-9 09:24 编辑

> 本帖最后由 啥都不会 于 2022-5-8 11:07 编辑

> 本帖最后由 啥都不会 于 2022-5-8 10:45 编辑

> 本帖最后由 啥都不会 于 2022-5-8 10:43 编辑

# 前言:

这篇笔记主要学习算数运算指令。

> 写汇编代码比较讲究细节,每个人思路不一样,写的代码自然也是不一样的。所以,建议大家先看题目自己验证写一遍代码,后续再看别人写的代码,并做好代码优化。

# 编程语言:

汇编语言

# 以下为主题内容:

笔记参考 "[杨季文《80x86汇编语言程序设计》](https://pan.baidu.com/s/1J-zm2cAeeDEENqKaLa-uxw?pwd=5jam)"

# 加法指令

## 普通加法指令(ADD)

普通加法指令格式如下:

> ADD OPRD1, OPRD2

这条指令两个操作数相加,结果送至目的操作数 OPRD1,即:

> OPRD1<== OPRD1 + OPRD2

ADD 指令对各标志位的影响:

!(https://user-images.githubusercontent.com/25861639/167234492-e04bcea5-4c69-4936-b4d5-a527b8007d00.png)

!(https://user-images.githubusercontent.com/25861639/167234618-08a0bfc3-b7cf-4833-b66e-49d8dbcffd00.png)

!(https://user-images.githubusercontent.com/25861639/167234644-c9191db5-d8ea-4fc8-9d8c-eb894485f897.png)

## 带进位加法指令(ADC)

带进位加法指令格式如下:

> ADC OPRD1, OPRD2

这条指令与ADD指令类似,完成两个操作数相加,但还要把进位标志CF的值加上去,把结果传送至目的操作数 OPRD1 即:

> OPRD1 <== OPRD1 + OPRD2 + CF

ADC指令的运用:

1. 验证 ADC 运算指令

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

!(https://user-images.githubusercontent.com/25861639/167235313-2278d1e5-0bdb-4862-8752-c4033cfcd0ee.png)

2. 计算超过 16 位数的加法

```
;计算 ffffh+1 的值
;DX:AX(高16位:低16位)
mov ax,ffff
add ax,1
adc dx,0
```

!(https://user-images.githubusercontent.com/25861639/167235478-50f07a10-6893-43d2-8e36-788eb825efb1.png)

```
;计算 1ef000h+201000h,结果存放在 AX 高 16 位,和 BX 低 16 位。
mov ax,001eh
mov bx,f000h
add bx,1000h
adc ax,0020h
```

!(https://user-images.githubusercontent.com/25861639/167236453-236d168a-ebb5-44a6-b55f-0ae9f604c2bd.png)

## 加1指令(INC)

加 1 指令格式如下:

> INC OPRD

这条指令完成对操作数 OPRD 加 1,然后把结果送回OPRD,即:

> OPRD <== OPRD+1

例如:

```
inc al
inc VARB
```

> 操作数可以是通用寄存器,也可以是存储单元。这条指令执行的结果影响标志 ZF、SF、OF、PF 和 AF,但不影响 CF。

1. 验证 INC 指令不影响 CF 位

!(https://user-images.githubusercontent.com/25861639/167241522-d67b389f-7b96-4921-a140-0ad56b7ec671.png)

> INC 指令确实不会影响 CF 位,那为什么会这样呢?

> 个人认为:因为 INC 指令通常用在循环语句中,且主要用于偏移地址的运算,偏移地址加 1 严格意义上来说并不属于算数运算指令,所以没有必要把 CF 位置为 1。如果再循环中 INC 指令把 CF 位置 1 了,这样会影响到算数运算指令的结果。

!(https://user-images.githubusercontent.com/25861639/167237077-6d62d3d0-0dca-4e2b-afa7-08d0a9f5557e.png)

!(https://user-images.githubusercontent.com/25861639/167237093-7f6ae13a-845c-4de1-9077-0e02de54ceb7.png)

# 减法指令

## 普通减法指令(SUB)

普通减法指令格式如下:

> sub OPRD1, OPRD2

这条指令完成两个操作数相减,从 OPRD1 中减去 OPRD2,结果送到目标操作数 OPRD1 中,即:

> OPRD1<== OPRD1 - OPRD2

SUB 指令对各标志位的影响:

!(https://user-images.githubusercontent.com/25861639/167242306-e8f907fe-efbe-4d33-a994-2966fb895dd8.png)

!(https://user-images.githubusercontent.com/25861639/167242324-bd58bb84-ef2e-4923-90cb-b02c4cce0df7.png)

## 带进(借)位减法指令(SBB)

带进(借)位减法指令:

> SBB OPRD1, OPRD2

这条指令与sub指令类似,再操作数 OPRD1 减去操作数 OPRD2 的同时好药减借位(进位)标志 CF 位的值,即:

> OPRD1<== OPRD1-OPRD2-CF

例如:计算 003E1000H-00202000H,结果放在AX:BX中

```
mov bx,1000h
mov ax,003eh
sub bx,2000h
sbb ax,0020h
```

!(https://user-images.githubusercontent.com/25861639/167242870-6837040b-3ab0-4378-9605-6e7137df6d01.png)

## 减 1 指令(DEC)

减1指令格式如下:

> DEC OPRD

这条指令把操作数 OPRD 减 1,并把结果送回 OPRD,即:

> OPRD <== OPRD-1

DEC 指令和 INC 的作用一样,且不会影响 CF 位。

## 取补指令(NEG)

!(https://user-images.githubusercontent.com/25861639/167243450-8a76dfc5-b259-4d62-b612-1a775fe39e87.png)

!(https://user-images.githubusercontent.com/25861639/167243468-9f731270-8d26-4113-81d8-02647664ad78.png)

## 比较指令(CMP)

!(https://user-images.githubusercontent.com/25861639/167243709-471e9ab4-6871-4583-b0da-8cf8ecf0b7a5.png)

# 乘法指令

在乘法指令中,被乘数是一个隐含的操作数AL(8位数相乘)或者 AX(16位数相乘)。乘数可以用除立即数以外的任何一种寻址方式。

## 无符号数乘法指令(mul)

无符号数指令格式:

> MUL OPRD

1. 如果 OPRD 是 8 位的字节操作数,则 AL*OPRD ,16 位结果送到 AX 中
2. 如果 OPRD 是 16 位的字操作数,则 AX*OPRD,32位结果传送到 DX:AX 中,DX 为高 16 位,AX 为低 16 位。

例如:

```
MUL BL
MUL AX
MUL VARW
```

> 如果乘积结果的高半部分(字节相乘时为 AH,字相乘时为 DX)不等于零,则标志位 CF=1,OF=1;否则CF = 0,OF = 0

## 有符号数乘法指令(imul)

有符号数指令格式:

> IMUL OPRD

1. 如果 OPRD 是 8 位的字节操作数,则 AL*OPRD ,16 位结果送到 AX 中
2. 如果 OPRD 是 16 位的字操作数,则 AX*OPRD,32位结果传送到 DX:AX 中,DX 为高 16 位,AX 为低 16 位。

例如:

```
IMUL CL
IMUL DX
IMUL VARW
```

> 如果乘积结果的高半部分(字节相乘时为 AH,字相乘时为 DX)不是低半部分的符号扩展,则标志位 CF=1,OF=1;否则CF = 0,OF = 0

# 除法指令

在除法指令中,被除数是一个隐含的操作数AX(除数是8位数)或者 DX:AX(除数是16位数)。除数可以用除立即数以外的任何一种寻址方式。

## 无符号数除法指令(div)

!(https://user-images.githubusercontent.com/25861639/167245498-1b8d2fa0-9e8d-4621-8d37-34bf7f26660b.png)

## 有符号数除法指令(idiv)

!(https://user-images.githubusercontent.com/25861639/167245526-424c3104-794b-463d-b59e-43b811d9f0ce.png)

## 符号扩展指令

由于除法指令隐含使用字被除数或双字被除数,所以在做除法操作之前需要先将 AH 或者 DX 重置,避免出现运算错误。为此8086专门提供了符号扩展指令。

### 字节转换为字指令(CBW)

!(https://user-images.githubusercontent.com/25861639/167246021-b64f8ed0-763d-4d84-b788-c2cd7a191ed5.png)

### 字节转换为双字指令(CWD)

!(https://user-images.githubusercontent.com/25861639/167246070-6626e539-7330-4f1d-bdd6-03b2f6ce5823.png)

!(https://user-images.githubusercontent.com/25861639/167246112-4a4f9944-b68e-4310-b1ac-79f47b990684.png)

1. 除数为0
   
   !(https://user-images.githubusercontent.com/25861639/167255372-7a57f6ec-cc98-4f03-8da3-5f2ab223a78d.png)
2. 除以8位,商超过 8 位,出现除法溢出。
   
   !(https://user-images.githubusercontent.com/25861639/167255174-74cf8e69-d7ff-48ec-b147-c6123e2ef3dd.png)

# 代码练习

分为两部分:完善代码片段+练习相关算数运算指令

## add.asm

```
;程序名:add.asm
;功能:计算 1234:5678H 开始的 100 个无符号数的和。把32位的和保存在 DX(高位)和 AX 寄存器中。

assume cs:code,ds:data

;增加一个变量来存储结果
data segment
result dd ?   ;定义一个未初始化的变量
data ends

code segment
start:
mov ax,data
mov ds,ax

;增加了ds 数据段,后面还要将结果存储到 ds: 中
;所以最好用 es 来存储段值 1234h
mov ax,1234h
mov es,ax

;计算前做好初始化工作
mov si,5678h
mov ax,0

;初始化操作,还需要考虑哪些标志位需要初始化。
;如果该程序作为子程序使用,父程序的标志位可能会影响到运算结果
;例如这里 "无符号数进位加法运算" 需要考虑到 CF 位的初始化。
;mov dx,ax;mov 指令不会使 CF 标志位置0

;使用逻辑运算指令将 dx 置为0
xor dx,dx
mov cx,100
next:
;si 默认用的是 ds 寄存器,所以这里要加上段前缀
add ax,es:
adc dx,0

;inc si
;inc si
;可替换为,因为这样写并不会影响到后面的指令执行。
add si,2
;cx=cx-1,如果 cx 不为 0,就跳到 next 标号处。这是做了一个循环。
dec cx
jnz next

;两个数据类型不一样,需要指定大小
mov word ptr result,ax
;一个地址对应一个字节,ax == 2字节,所以偏移地址要+2
mov word ptr result+2,dx

mov ax,4c00h
int 21h
code ends
        end start
```

### add.asm 优化

1. 定义一个变量验证执行结果
2. 增加 "复制字符串" 功能
3. 最后运算结果在 DX:AX 中,并存储到变量 result 中
4. 打印 result 变量中存储的结果

```
;程序名:add1.asm
;功能:计算 1234:5678H 开始的内存中的20个16位无符号整数的和
;注意有进位值,结果保存到 DX:AX,或者 result 变量中

;程序设计:
;1、自定义一个number,将 20 个 16 位无符号整数,复制到 1234:5678H 处,主要是为了验证代码执行结果是否正确。
;2、计算整数之和使用 add 指令,如果两数之和超过ax寄存器(CF 有进位),使用 ADC 指令将最高位存到DX中
;3、使用循环执行该段代码,直到运算结束。
;4、将 DX:AX 中的结果,存放到 result 变量中,result 定义为双字 dd。
;5、使用循环将 result 中的结果,以16进制打印出来

;====================================
assume cs:code,ds:data

data segment
number dw '10',0ABh,0ABh,'40','50','60','70','80','90','00','11','22','33','44','55','66','77','88','99','00'
result dd ?
buffer db 0,0
buffer1 dw 0,0
stop db '$'
data ends

code segment
start:
;复制字符串
;========================================
        mov ax,data
        mov ds,ax
        mov ax,1234h
        mov es,ax
        mov si,offset number
        mov bx,5678H
        mov di,bx
        mov cx,20
        rep movsw                                ;si寄存器,默认使用ds段寄存器;di寄存器,使用 es 段寄存器


;计算结果,并存储到 result 中
;===============================================================
        mov ax,0
        mov dx,0
        mov bx,0
        mov di,5678h
        mov cx,20
sum:
;计算16位无符号整数
        clc
        add ax,word ptr es:
        JB ADC1
ADC1:
;判断并计算进位
        adc dx,0
        inc di
        inc di
        dec cx
        jnz sum
;复制结果到result
        mov word ptr result,ax
        mov word ptr result+2,dx


;将结果以16进制显示
;===================================================================
        mov cx,4                                        ;定义循环次数(每次循环打印1字节)
        push cx                                                ;堆栈中保存循环次数
        mov bx,3                                        ;定义要打印字符串的指针位置(从最高位开始)
print:
        xor ch,ch
        pop cx               
        push cx                                                ;使用并保存循环次数
        xor ax,ax
        mov si,offset result                ;从高位开始取值
        mov al,byte ptr
        push ax

;计算高4位
        mov cl,4
        shr al,cl
        cmp al,0Ah                                        ;如果高4位是字母就转换成字母字符
        jnb char1
        add al,30h                                        ;如果高4位是数字就转换成数字字符
        mov buffer,al                                ;此时4位扩展成8位,将结果存储到 buffer 的低8位
        jmp low1                                        ;高4位计算结束,跳转到低4位计算
char1:
        add al,37h                                        ;此时4位扩展成8位,将结果存储到 buffer 的低8位
        mov buffer,al

low1:
;计算低4位
        pop ax
        and al,0Fh
        cmp al,0Ah                                        ;如果低4位是字母就转换成字母字符
        jnb char2
        add al,30h                                        ;如果低4位是数字就转换成数字字符
        mov buffer+1,al                                ;此时4位扩展成8位,将结果存储到 buffer 的高8位
        jmp loops
char2:
        add al,37h
        mov buffer+1,al                                ;此时4位扩展成8位,将结果存储到 buffer 的高8位

;将字符以大端形式打印出来
loops:
;内循环,传输高16位
        dec bx                                                ;循环一次,指针减1
        pop cx
        dec cx
        push cx                                                ;计算并保存循环次数
        pushf
        test cx,01h
        jz loops1                                        ;判断此次是内循环还是外循环
        ;
        mov ax,word ptr buffer
        mov buffer1,ax                                ;把buffer中的高16位,传给 buffer1 的低16位
        ;
        popf
        jnz print                                        ;判断循环次数是否等于0,不等于0,则继续循环

loops1:
;外循环传输低 16 位,并打印出来
        mov ax,word ptr buffer
        mov buffer1+2,ax                        ;把buffer中的低16位,传给 buffer1 的高16位
        mov dx,offset buffer1
        mov ah,09h
        int 21h                                                ;打印循环结果
        popf
        jnz print                                        ;判断是否循环结束
        mov ax,4c00h
        int 21h
code ends
        end start
```

## sub.asm

```
;程序名:sub.asm
;功能:两个 64 位数按照高高低低的原则分别存放到 DATA1 和 DATA2 两个缓冲区中
;计算 DATA1-DATA2
;DATA1 = 2000156781234
;DATA2 = 1000212345678

assume cs:code,ds:data

data segment
data1 dw 6AB2h,0B2A2h,01D1h,0
data2 dw 334Eh,0E14Dh,00E8h,0
result dd 0
data ends

code segment
start:
    mov ax,data
    mov ds,ax

    ;优化后程序片段
    xor si,si       ;16位汇编,数据默认从 ds:0 处开始存储
    mov cx,4

    ;源程序片段
    ;mov cx,4
    ;mov bx,6
sub1:
    ;mov si,offset data1
    ;mov di,offset data2
    ;mov ax,
    ;sbb ax,
    ;mov si,offset result
    ;mov word ptr ,ax


    ;编译器会将标号变成偏移地址,所以定位字符串的时候可使用 == 标号
    mov ax,data1
    ;在运算加法和减法(有符号)时要注意使用 adc 和 sbb 这种带进位和借位的运算指令。
    sbb ax,data2
    mov word ptr result,ax
    ;这里使用 add,是因为cx判断在下面,并不影响 ZF 标志寄存器
    add si,2
    dec cx
    jnz sub1

    ;sub bx,2
    ;dec cx
    ;jnz sub1
    ;
    mov ax,4c00h
    int 21h
code ends
    end start
```

## mul.asm

```
;程序名:mul.asm
;功能:乘法运算

;imul(有符号数乘法)、mul(无符号数乘法)
;两条指令都是对AL、AX 执行无符号数乘法操作
;=================================
assume cs:code,ds:data

data segment
val1 db 2
val2 db 9
val3 db 20
data ends

code segment
start:
    mov ax,data
    mov ds,ax
    ;8位数乘法会影响 AX,16位数乘法会影响 DX:AX,这点非常重要(特别时16位乘法运算)。
    ;也就是说在做16位乘法时,要少使用dx,不然会影响代码的结果。
    ;如果乘数是 8 位,被乘数在 al 中,积在 ax 中。
    mov al,val1
    mul val1
    mov bl,val2
    mul bl
    ;如果乘数是 16 位的,被乘数在 ax 中,积在 DX:AX 中。
    mov ah,1
    mov al,val3
    mov bx,10
    mul bx
    ;
    mov ax,word ptr val1
    mov cx,-1
    mul cx
    ;如果乘积扩展到 DX 中,则 CF、OF 置为 1,如果 DX=FFFF 说明这是符号位,CF、OF 为 0
    mov bx,word ptr val2
    mov ax,-1
    imul bx
    ;
    mov ax,4c00h
    int 21h
code ends
end start
```

## div.asm

```
;程序名:div.asm
;功能:除法运算
;=================================
assume cs:code,ds:data

data segment
val1 db 2
val2 db 9
data ends

code segment
start:
    mov ax,data
    mov ds,ax
    ;
    mov al,val1
    mov bl,val2
    xor ah,ah         ;无符号数除8位数,使用 xor 指令扩展ah
    div bl
    ;
    mov ax,-1
    xor dx,dx         ;无符号数除16位数,使用 xor 指令扩展dx
    mov bx,word ptr val1
    div bx
    ;
    mov ax,-1
    cwd               ;有符号数除16位数,使用 CWD 指令扩展符号位dx
    mov bx,word ptr val2
    idiv bx
    ;
    mov ax,4c00h
    int 21h

code ends
end satrt
```

## summary.asm

算数运算代码总结

```
;程序名:summary.asm
;功能:算数运算总结。计算表达式:(X*Y+Z-1024)/75
;假设其中的X、Y和Z均为16位带符号数,分别存放在名为XXX、YYY和ZZZ的
;变量单元中。计算结果保存在 AX 中,余数保存在 DX 中。
;===================================================================

assume cs:code,ds:data

data segment
XXX dw 512
YYY dw -2
ZZZ dw 2203
chus dw 75
data ends

code segment
start:
    mov ax,data
    mov ds,ax

    mov ax,XXX
    ;mov bl,byte ptr YYY    ;错误 512=200h 是字大小
    ;imul bl
    imul YYY
    ;add 指令本身会对 CF 位初始化,所以不需要其他操作
    add ax,ZZZ
    ;加法可能出现进位,执行该指令之前还需要考虑CF位初始化
    adc dx,0
    ;减法要考虑借位
    sub ax,1024
    sbb dx,0
    ;有符号数除法考虑扩展符号位
    cwd
    idiv chus

    mov ax,4c00h
    int 21h

code ends
    end start
```

iytwfx01 发表于 2022-5-8 10:42

感谢楼主

yRAEnGO34 发表于 2022-5-8 10:43

感谢楼主

osLHEYQ3768 发表于 2022-5-8 10:43

我现在已经把楼主作为我的学习目标了!

zwVqG0972 发表于 2022-5-8 10:45

谢谢分享

GSkulFOjfAW 发表于 2022-5-8 10:47

感谢楼主

wLlqmUDo49 发表于 2022-5-8 10:55

大佬无敌

DGY24736 发表于 2022-5-8 10:58

膜拜大神!

mSYxEdBpL 发表于 2022-5-8 11:00

不知道来晚了没有

eoCqP 发表于 2022-5-8 11:02

谢谢分享
页: [1] 2 3 4 5 6 7 8
查看完整版本: 8086汇编-算数运算指令