8086汇编-算数运算指令
9unk Lv5

加法指令

普通加法指令(ADD)

普通加法指令格式如下:

ADD OPRD1, OPRD2

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

OPRD1<== OPRD1 + OPRD2

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

带进位加法指令(ADC)

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

ADC OPRD1, OPRD2

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

OPRD1 <== OPRD1 + OPRD2 + CF

ADC指令的运用:

  1. 验证 ADC 运算指令

    1
    2
    3
    4
    mov ax,2
    mov bx,1
    sub bx,ax
    adc ax,1

    4

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

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

    5

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

6

加1指令(INC)

加 1 指令格式如下:

INC OPRD

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

OPRD <== OPRD+1

例如:

1
2
inc al
inc VARB

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

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

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

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

7
8

减法指令

普通减法指令(SUB)

普通减法指令格式如下:

sub OPRD1, OPRD2

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

OPRD1<== OPRD1 - OPRD2

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

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

带进(借)位减法指令:

SBB OPRD1, OPRD2

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

OPRD1<== OPRD1-OPRD2-CF

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

1
2
3
4
mov bx,1000h
mov ax,003eh
sub bx,2000h
sbb ax,0020h

12

减 1 指令(DEC)

减1指令格式如下:

DEC OPRD

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

OPRD <== OPRD-1

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

取补指令(NEG)

13
14

比较指令(CMP)

15

乘法指令

在乘法指令中,被乘数是一个隐含的操作数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 位。

例如:

1
2
3
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 位。

例如:

1
2
3
IMUL CL
IMUL DX
IMUL VARW

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

除法指令

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

无符号数除法指令(div)

16

有符号数除法指令(idiv)

17

符号扩展指令

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

字节转换为字指令(CBW)

18

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

19
20

  1. 除数为0
    22

  2. 除以8位,商超过 8 位,出现除法溢出。
    21

代码练习

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

add.asm

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
;程序名: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:[result] 中
;所以最好用 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:[si]
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 变量中存储的结果
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
;程序名: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:[di]
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 [si+bx]
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
;程序名: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,[si+bx]
;sbb ax,[di+bx]
;mov si,offset result
;mov word ptr [si+bx],ax


;编译器会将标号变成偏移地址,所以定位字符串的时候可使用 [si+标号] == 标号[si]
mov ax,data1[si]
;在运算加法和减法(有符号)时要注意使用 adc 和 sbb 这种带进位和借位的运算指令。
sbb ax,data2[si]
mov word ptr result[si],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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
;程序名: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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
;程序名: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

算数运算代码总结

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
;程序名: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
  • 本文标题:8086汇编-算数运算指令
  • 本文作者:9unk
  • 创建时间:2022-05-07 09:49:05
  • 本文链接:https://9unkk.github.io/2022/05/07/8086-hui-bian-suan-shu-yun-suan-zhi-ling/
  • 版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!