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
```
感谢楼主 感谢楼主 我现在已经把楼主作为我的学习目标了! 谢谢分享 感谢楼主 大佬无敌 膜拜大神! 不知道来晚了没有 谢谢分享