🕐 🕑 🕒 🕓 🕔 🕕 🕖 🕗 🕘 🕙 🕚 🕛
几乎所有 UNIX 指令都在这里了
一本 UNIX 康熙大字典 …
文章目录
这一篇 “指令系统(下)” 写了 3 天,二万五千字,终于写完了…🍋
因为分 3 天写完的,所以前几千和后两万字表达形式有一定的不同。如有笔误,可以留言。
上一篇文章链接: 【计算机原理和汇编原理④】——指令系统(上).
下一篇文章链接: 【计算机和汇编原理⑥】——UNIX的编程基础【SEGMENT、ASSUME、PROC、ORG、END】.
四、汇编语言语句类型和格式
1、概述
● 可执行文件的生成过程:
① 编辑
② 编译
③ 链接
● 汇编语言源程序包括的语句类型为:指令性语句和指示性语句。
● 指令性语句即为通常所说的符号指令。符号指令:经汇编后,其机器指令通知 CPU 进行什么操作。
● 指示性语句包括伪指令和宏指令。 伪指令:是非机器指令,是在汇编链接期间进行操作的。为汇编程序,链接程序提供汇编链接信息
● 符号指令和伪指令区别:
语句类型 | 符号指令(机器指令) | 伪指令 |
---|---|---|
执行者 | CPU | 汇编链接工具 |
功能 | 完成某个特定的 CPU 操作,例如:把两个寄存器中的数相加 | 为汇编链接工具提供信息,例如:计算出某个逻辑段的段基址 |
● 指令性语句(符号指令)的格式为:
● 指示性语句(伪指令)的格式为:
◆ 说明:
标号名、变量名命名规则:以除数字以外的字母或符号开头,后跟字母、数字…长度 ≤ 31个字符。
2、数据定义伪指令
2.1 字节字义伪指令 DB
● 书写格式:变量名 + 空格 + DB + 空格 + 一串用逗号间隔的单字节数。举例如下:
N
1
D
B
12
H
,
64
,
−
1
,
3
∗
3
N1\quad\quad DB\quad 12H,\,\,64,\,\,-1,\,\,3*3
N1DB12H,64,−1,3∗3
D
B
01010101
B
,
‘
A
’
,
‘
B
’
\quad\quad\quad\,\, DB\quad 01010101B,\,\, ‘A’ ,\,\, ‘B’
DB01010101B,‘A’,‘B’
D
B
0
A
6
H
,
‘
H
E
L
L
O
’
\quad\quad\quad\,\, DB\quad 0A6H,\,\, ‘ HELLO’
DB0A6H,‘HELLO’
N
2
D
B
?
,
?
,
?
;
与
下
条
等
价
N2\quad\quad DB\quad ?,\,\,?,\,\,?\quad\quad ;与下条等价
N2DB?,?,?;与下条等价
N
2
D
B
3
D
U
P
(
?
)
N2\quad\quad DB\quad 3\quad DUP(?)
N2DB3DUP(?)
◆ 说明:
“ DB ” —— Define Byte 的缩写,表示 “定义字节” 。
“ ? ” —— 随机数, Dup ~ Duplicate(重复)
“ 3 Dup(?) ” 代表 3 个用逗号间隔的随机数
“ 5 Dup(‘A’) ” 代表 5 个用逗号间隔的 ‘A’ 的 ASCII 码
◆ “汇编” 功能:
① 通知汇编程序把 DB 后跟的单字节数,依次存入从变量(如N1)开始的单元,负数用补码表示,单引号中的字符翻译成 ASCII 码。用 DB 定义的这些内存单元(N1~ N1+12,N2 ~ N2+2)的属性均为 “字节型” 。
② 源代码中,不管二进制还是十进制,还是像 ‘A、B、…’ 等字符,汇编后都会转换成十六进制。
2.2 字定义伪指令 DW
● 书写格式:变量名 + 空格 + DW + 空格 + 一串用逗号间隔的双字节数。举例如下:
W
N
U
M
D
W
1234
H
,
12
,
‘
A
B
’
,
‘
C
’
WNUM \quad DW \quad 1234H,\,\,12, \,\,‘AB’, \,\,‘C’
WNUMDW1234H,12,‘AB’,‘C’
W
N
U
M
D
W
?
,
?
WNUM\quad DW \quad ?,\,\,?
WNUMDW?,?
W
N
U
M
D
W
2
D
U
P
(
?
)
WNUM\quad DW \quad 2 \quad DUP(?)
WNUMDW2DUP(?)
◆ 说明:
“ DW ” —— Define Word “字定义”
“ ? ” —— 双字节随机数
单引号中只能是一个或两个字符
◆ 汇编功能::
① 通知汇编程序把 DW 后跟的双字节数,依次存入从变量名开始的单元。每一个数占 2 个字节,低位字节 → 低址单元,高位字节 → 相邻的高址单元。
② 用 DW 定义的这些单元的属性都是 “字型”,如上例,“ WNUM ~ WNUM+11 ” 这 12 个单元的属性都是 “字型” 。
2.3 双字定义伪指令 DD
● 书写格式:变量名 + 空格 + DD + 空格 + 一串用逗号间隔的 4 字节数。举例如下:
D
N
U
M
D
D
12345678
H
DNUM \quad DD \quad 12345678H
DNUMDD12345678H
◆ 汇编功能:
① 通知汇编程序把 DD 后跟的数存入变量名开始的单元,每一个数占 4 个字节。低位字节 → 低址单元,高位字节 → 高址单元。
② 用 DD 定义的这些单元的属性都是 “双字型”,上例中 DNUM ~ DNUM+3 这4个存储单元,都是属于双字型单元。
2.4 多字节定义伪指令
● 书写格式:
① 变量名 + 空格 + DF + 空格 + 一串用逗号间隔的 6 字节数
② 变量名 + 空格 + DQ + 空格 + 一串用逗号间隔的 8 字节数
③ 变量名 + 空格 + DT + 空格 + 一串用逗号间隔的 10 字节数
◆ 汇编功能:通知汇编程序为 DF/DQ/DT 后跟的每一个数,分配 6/8/10 个单元。
3、符号定义伪指令
3.1 等值伪指令 EQU
● 书写格式:符号常数 + 空格 + EQU + 空格 + 表达式。举例如下:
N
U
M
E
Q
U
33
NUM\quad EQU\quad 33
NUMEQU33
◆ 汇编功能: 定义符号常数 NUM 的值为 33
3.2 等号伪指令 =
● 书写格式:符号常数 + 空格 + “=” + 空格 + 表达式。举例如下:
N
U
M
=
33
NUM\, = \, 33
NUM=33
◆ 汇编功能: 定义符号常数 NUM 的值为33
“EQU” 与 “=” 的区别: 用 EQU 定义的符号常数,其值在后继语句中不能更改(也就是说一开始定义后,后面就不可以改了);用 “=” 定义的符号常数,其值在后继语句中可以重新定义。
4、常用的运算符
4.1 运算符 $
◆ 功能:$ 运算符可以返回汇编计数器的当前值,因为汇编程序对源程序是逐行汇编的。$ 运算符紧跟在 DB、DW、DD伪指令之后,可以统计字符串的长度。
例如数据段有:
B
U
F
D
B
′
T
H
E
Q
U
I
C
K
B
R
O
W
N
F
O
X
′
;
字
符
串
长
度
19
BUF\quad DB\quad 'THE\,\,\,\, QUICK\,\,\,\, BROWN\,\,\,\, FOX'\quad\quad ;字符串长度19
BUFDB′THEQUICKBROWNFOX′;字符串长度19
LLL EQU $-BUF
汇编后,符号常数 LLL 的值即为 19
4.2 运算符 SEG
● 书写格式:SEG 段名或变量名或标号名
◆ 功能:计算某一逻辑段的段基址。举例如下:
M
O
V
A
X
,
S
E
G
D
A
T
A
MOV\quad AX,\,\, SEG\quad DATA
MOVAX,SEGDATA
M
O
V
D
S
,
A
X
MOV\quad DS,\,\, AX
MOVDS,AX
设 “DATA” 是数据段的段名,上述两条指令算出数据段的段基址,先赋给 AX,再转赋 DS。
4.3 运算符 OFFSET
● 书写格式:OFFSET 变量名或标号名
◆ 汇编功能:算出某个变量或标号名所在单元的相对于段首的偏移地址(即有效地址)。举例如下:
设以 “DATA” 为段名的数据段中,存储了名称为 “BUF” 的变量(装了 3 字节的数据):BUF DB 12, 34, 56
"然后执行以下程序段:"
MOV AX, SEG DATA ; 计算 DATA 所在逻辑段的段基址,传给寄存器 AX
MOV DS, AX ; 再将 AX 里的数据传给数据段寄存器 DS
MOV BX, OFFSET BUF ; 计算 BUF 所在单元相对于段首的偏移地址,值传给基地址寄存器 BX
MOV AL, [BX] ; 也可写为 “MOV AL, DS:[BX]” ,最后 AL = 12
4.4 运算符 PTR(这一章的难点,灵活记就行)⭐️
● 书写格式:类型说明符 PTR 地址表达式
◆ 功能:在本条指令中临时修改地址表达值的属性(它有点像 C 语言里面的强制转换,只不过是临时的)。
① 在双操作数指令中(如:MOV、ADD、SUB、CMP、…),规则如下:
(1) 源操作数为立即数,目标为直接寻址的内存操作数。当二者类型属性不一致时,后者必须用 PTR 临时修改其属性,使源目两个操作数类型属性一致。
(2) 源为单字节/双字节的立即数,目标操作数为间址、变址、基址或基址加变址寻址的内存操作数,无论两者类型属性是否已经一致,后者都必须用 PTR 显示说明其属性,使其与源操作数属性一致。
(3) 源操作数、目标操作数中有一方为直接寻址的内存器操作数,但二者类型属性不一致,必须用 PTR 临时修改其中存储器操作数的属性 。
② 在单操作数指令中(如:INC、DEC、…),规则如下:
(1) 操作数为间址、变址、基址或基址加变址寻址的存储器操作数,必须用 PTR 说明是字节操作,字操作,还是双字操作(具体要根据使用该条指令操作的意图)。
(2) 操作数是直接寻址方式的存储器操作数,是否使用 PTR 要看:指令对操作数的类型属性要求是否与操作数的类型属性一致(例: PUSH 指令)或依据该条指令的操作意图(按照字节方式?字方式?等)。
◆ 举例如下:
"首先说明:2 字节即为 1 个 “字”(word)"
设数据段有:
BUF DB 11,22,33,44 ; 十进制(最好把这些十进制的数转换为十六进制)
WBUF DW ?,? ; 双字节
XX DB 0FFH,0
YY DB 0FFH,0FFH,0,0
ZZ DB 0FFH,0FFH,0FFH,0
"执行代码段一:"
MOV AX, BUF ; (×) 因为它违反了双操作数的第(3)条规则:目标 AX 是字属性(WORD), 而源 BUF 是字节属性(BYTE)
MOV AL, BUF ; (√) AL = 11
MOV AX, WORD PTR BUF ; (√) AH = 22, AL = 11
"重新执行代码段二:"
MOV BUF,12H ; (√)
MOV BUF,1234H ; (×) 因为它违反了双操作数第(1)条规则:目标 BUF 是字节属性(BYTE), 而源 1234H 是字属性(WORD)
MOV WORD PTR BUF, 1234H ; (√) BUF 的单元为 34H, BUF+1 的单元为 12H
"重新执行代码段三:"
MOV BX,OFFSET XX ; 把 XX 的偏移地址取出来到基地址寄存器 BX 中
MOV SI,OFFSET YY ; 把 YY 的偏移地址取出来到源索引寄存器 SI 中
MOV DI,OFFSET ZZ ; 把 ZZ 的偏移地址取出来到目标索引寄存器 DI 中
MOV [BX], 12H ; (×) 因为它违反了双操作数第(2)条规则:虽然目标 [BX] 和 源 12H 都是字节属性(BYTE), 但还是要用 PTR 才行
MOV BYTE PTR [BX],12H ; (√) XX 单元为12H
MOV WORD PTR [BX],12H ; (√) XX 单元为12H, XX+1 单元为0。这样也对,因为立即数在指令会自动扩展(从低字长 → 高字长), 但不能反过来
MOV [SI],1234H ; (×) 因为它违反了双操作数第(2)条规则:虽然目标 [SI] 和 源 1234H 都是字节属性(BYTE), 但还是要用 PTR 才行
MOV WORD PTR [SI],1234H ; (√)
MOV AX,[BX] ; (√) 这个包括以下 2 个比较特殊, 规则里没有,所以我们默认是对的
MOV AL,[SI] ; (√)
MOV [DI],AX ; (√)
INC [BX] ; (×) 因为它违反了单操作数第(1)条规则:目标 [BX] 为间接寻址, 必须加 PTR
INC [SI] ; (×) 同上, 违反了单操作数第(1)条规则
INC [DI] ; (×) 同上, 违反了单操作数第(1)条规则
INC BYTE PTR [BX] ; (√) [BX] 的第一个单元的值 +1 (因为是以字节属性来执行)
INC WORD PTR [SI] ; (√) [SI] 的第一个和第二个单元的值 +1 (因为是以字属性来执行)
INC DWORD PTR [DI] ; (√) [DI] 的第一个、第二个、第三个、第四个单元的值 +1 (因为是以双字属性来执行)
4.5 方括号运算符 [ ]
● 用方括号括起来的地址表达式是访问内存操作数常用的寻址方式,方括号的另一用途是标注数组元素的下标,下标从 0 开始。
◆ 举例如下:
假设数据段为:
BUF DB 11,22,33,44
代码段为:
MOV AL,BUF[3] ; 访问 BUF数据段的第“3”个单元(下标分别为0、1、2、3), 即把 “44” 找出来传给 累加寄存器(低8位)AL
4.6 算术运算符、逻辑运算符、关系运算符
◆ 补充说明:这些运算,是在把这些汇编指令变成机器指令之前,就会计算完。也就是在 “编译” 完后,这些运算符都不见了,只有结果,然后再接着把这些结果转换为机器指令。
● 算术运算符:“ +、-、*、/ ”
● 逻辑运算符:“ AND(与)、OR(或)、XOR(异或)、NOT(非)、SHL(左移位)、SHR(右移位) ”
● 关系运算符:“ EQ(等于)、NE(不等于)、GT(大于)、LT(小于)、GE(大于或等于)、LE(小于或等于) ”
◆ 举例如下:
MOV AH, (2*3+4)/10 ; 执行结果为 AH = 4
MOV AX, 1000H GT 1234H ; 执行结果为 AX = 0 【注:指令汇编后,如果为真,则结果为FFFFH,如果为假,则结果为0】
五、汇编语言语句类型和格式(指令系统中最重要的)
● 对于双操作数指令的说明(如:MOV,ADD,CMP…):
① 源、目操作数不可以同时为内存操作数
② 源、目操作数属性必须一致(即长度相同)
③ 当操作数中出现内存操作数时,注意是否需要使用 PTR 。
● 对于单操作数指令的说明(如:INC,DEC…):当操作数为内存操作数时,注意是否需要
使用 PTR 。
◆ 说明:为了方便,后面使用一些符号代替某些操作数。
① N:代表立即数。(比如 N8、N16、N32 分别代表 8、16、32 位的立即数)
② R:代表寄存器操作数。(比如 R8、R16、R32 分别代表 8、16、32 位寄存器操作数)
③ M:代表内存操作数。(比如 M8、M16、M32 代 表 8、16、32 位内存操作数)
④ S:代表段寄存器。
1、通用传送类指令
● 传送类指令不是“搬”过去,而是“复制”过去。且传送类指令执行后,不影响状态标志。
1.1 数据传送 MOV
● 功能:源 → 目,源不变,不影响 6 种状态标志(A、C、O、P、S、Z,内容详见上篇④——标志寄存器)
● 格式: MOV 目标寄存器, 源寄存器
"举例:"
MOV R/M, N
MOV R/M/S, R ;注:若 “R/M/S” 选的是,则目标不允许是代码段寄存器 CS
MOV R/M, S
MOV R/S, M ;注:若 “R/M/S” 选的是,则目标不允许是代码段寄存器 CS
◆ 说明:
① CS 不能做目标,不能向段寄存器写入立即数
① 禁止 2 个内存单元直接传送
① 源、目的属性要一致
◆ 接下来举 10 个🌰(栗子) 来吃透它如下:
"首先设数据段为:"
BNUM DB 12H, 34H, 56H, 78H, 90H ; 5 个字节属性的单元构成的数据段
WNUM DW 1122H, 3344H, 5566H ; 3 个字属性的单元构成的数据段
DNUM DD 13572468H, 87654321H ; 2 个双字属性的单元构成的数据段
FNUM DF 112233445566H ; 1 个float属性(连续六个字节)的单元构成的数据段
"第一个代码段:"
MOV AX, SEG 数据段段名
MOV DS, AX ; 对 DS 初始化
MOV BL, BNUM ; 数据段经过汇编之后, BL = 12H
"第二个代码段: "
MOV AX, SEG 数据段段名
MOV DS, AX
MOV BX, WNUM+2 ; 注:“WNUM” 指向 “22H”, “WNUM+1” 指向 “11H”,“WNUM+2” 指向 “44H”(小端计数法)
; WNUM 和 BX 都是字属性, 符合双操作数第(3)条规则, 故数据段经过汇编之后, BX = 3344H
"第三个代码段: "
MOV AX, SEG 数据段段名
MOV DS, AX
MOV EBX, DNUM+4 ; 注:“DNUM” 指向 “68H”, “DNUM+1” 指向 “24H”,“DNUM+2” 指向 “57H”...(小端计数法)
; 数据段经过汇编之后, 所以,EBX = 87654321H
"第四个代码段: "
MOV AX, SEG 数据段段名
MOV DS, AX
MOV BL, BYTE PTR DNUM ; 用 PTR 指令的情况(临时修改)。数据段经过汇编之后, BL = 68H
; 注:BL 是字节属性, DNUM 是双字属性
"第五个代码段: "
MOV AX, SEG 数据段段名
MOV DS, AX ; 对DS初始化
MOV BX, WORD PTR BNUM+1 ; 数据段经过汇编之后, BX = 5634H
; 注:BX 是字属性, BNUM 是字节属性
"第六个代码段: "
MOV AX,SEG 数据段段名
MOV DS,AX ; 对DS初始化
MOV EBX, DWORD PTR WNUM+1 ; 数据段经过汇编之后, EBX = 66334411H
; 注1:EBX 是双字属性, WNUM 是字属性
; 注2:“WNUM、WNUM+2、W..+3、..+4、..+5、..+6” 分别指向 “22H、11H、44H、33H、66H、55H”
"第七个代码段: "
MOV AX, SEG 数据段段名
MOV DS, AX
MOV BX, WORD PTR DNUM[3] ; 数据段经过汇编之后, BX = 2113H
; 注:DNUM[3] 是双字属性, BX 是字属性
"第八个代码段:"
MOV AX,SEG 数据段段名
MOV DS,AX ; 对DS初始化
MOV BX, WORD PTR FNUM ; 数据段经过汇编之后, BX = 5566H
; 注:FNUM 是float字属性(有6个字节), BX 是字属性(4个字节)
"第九个代码段:"
MOV AX, SEG 数据段段名
MOV DS, AX ; 对DS初始化
MOV BX, 3
MOV BL, BNUM[BX] ; 数据段经过汇编之后, BL = 78H
; “BNUM[BX]” 和 “BNUM[3]、BNUM+3” 的写法等价,是直接寻址
"第十个代码段: "
MOV AX, SEG 数据段段名
MOV DS, AX ; 对DS初始化
MOV SI, OFFSET BNUM ; BNUM 的偏移地址先 “送给” SI
MOV BX, [SI+1] ; 数据段经过汇编之后, BX = 5634H
; 这里是变址寻址。源索引寄存器 SI 是字属性,
1.2 有效地址传送 LEA
● 功能:计算内存单元的有效地址(不是其中的操作数) → 目标
● 格式:LEA 目标寄存器 , 源操作数
"一般用于:"
LEA R16/R32, 内存地址表达式
"举例:"
LEA BX, BUF ;将 BUF 单元的有效地址 → BX
LEA BX, [SI+5] ;将源索引寄存器 SI+5 变址的那个单元的有效地址 → BX
注:有效地址就是偏移地址, LEA指令 等效于 OFFSET运算符
比如:"LEA BX,BUF" 等效于 "MOV BX, OFFSET BUF"
1.3 交换指令 XCHG
● 功能:完成 2 个操作数的互换
● 格式:XCHG 第一操作数,第二操作数
"一般用于:"
XCHG R, R
XCHG M, R
XCHG R, M
"举例:"
XCHG AX, BX ;完成 AX 和 BX 的内容互换
◆ 额外说明:
① 段寄存器、立即数不能参加互换
② 若 2 个都是内存操作数则不行,且源、目的类型需要一致
1.4 堆栈操作指令
[1] 基本概念
● 堆栈的基本概念: 先进后出。就像一个封了底的羽毛球筒,最先放进去的羽毛球,最后才能拿出来。
● 计算机中的堆栈是人为设置的一片连续内存区,用来存放数据。所存数据按先进后出规律存取。计算机中的堆栈包含一下这些元素:

① 栈顶:栈区的低地址
② 栈底:栈区的高地址
③ 堆栈段寄存器(SS):存放堆栈段段基址
④ 堆栈指针ESP(SP):存放栈顶单元的偏移地址
⑤ SS、ESP(SP)初值,由程序员赋值或 DOS 系统自动赋值
⑥ 堆栈指针 SP 的初值决定了堆栈的大小,SP 始终指向堆栈的顶部,即始终指向最后压入堆栈的信息所在的单元。(图中,斜线的两个方块就代表放入的两段数据)
● 数据进栈过程(以 16 位操作数进栈为例):
● 数据出栈过程(以 16 位操作数进栈为例):
◆ 补充说明:80X86 的堆栈是向低地址方向延伸的,栈顶是 “浮动” 的。而且一次进栈、出栈的数据至少是 2 字节。
[2] 堆栈指令( PUSH POP )
● 进栈指令:PUSH
● 格式:PUSH 源操作数
"一般格式有:"
PUSH N16/N32/S/R16/R32/M16/M32
说明:当是非直接寻址的内存操作数时, 必须用 PTR 说明属性。
"举例:"
PUSH WORD PTR [BX]
PUSH DWORD PTR [SI+5]
● 出栈指令:POP
● 格式:POP 目标操作数
"一般格式有:"
PUSH R16/R32/M16/M32/S(若是S的话,不能是代码段寄存器CS)
说明:当是非直接寻址的内存操作数时, 也必须用 PTR 说明属性。
"举例:"
PUSH AX
POP BX ; BX = AX
PUSH WORD PTR [BX]
● 16 位标志寄存器进栈/出栈的比较特殊的指令: PUSHF、POPF【专门用于 16 位的标记寄存器,在中断的时候用的多】
● 16 位寄存器进栈/出栈的比较特殊的指令:PUSHA【用于“保护现场”,即依次把 AX、CX、DX、BX、SP、BP、SI、DI 压入栈】、POPA【用于“恢复现场”,即依次把 DI、SI、BP、SP、BX、DX、CX、AX 弹出栈】
2、算术运算类指令
2.1 二进制加法 ADD
● 格式: ADD 目标操作数,源操作数
● 功能:ADD :源 + 目→目
2.2 二进制减法 SUB
● 二进制减法: SUB 目标操作数,源操作数
● 功能 :目 - 源 → 目
2.3 二进制加进位法 ADC
● 格式:ADC 目标操作数,源操作数
● 功能:源 + 目 + 上条指令执行后的 C 标志 → 目
2.4 二进制减进位法 SBB
● 格式:SBB 目标操作数,源操作数
● 功能:目 – 源 – 上条指令执行后的 C 标志 → 目
"以上 4 种算术运算的一般格式有:"
ADD/SUB/ADC/SBB R/M, N/R
ADD/SUB/ADC/SBB R, M
◆ 说明:
① 源、目操作数的属性(长度)要一致 。
② 若操作数中有内存操作数时,请注意是否需要使用 PTR 运算符。
③ 这四种操作都影响 A、C、O、P、S、Z 标志。
"举例一:"
ADD [BX] , 12H ; (×) ∵12H 可以理解为是 0012H, 或 00000012H , 汇编程序无法肯定它的具体长度, 故要用 PTR 说明
ADD BYTE PTR [BX] , 12H ; (√)
ADD WORD PTR [BX] , 12H ; (√)
"举例二, 设数据段: "
FIRST DD 11223344H
SECOND DD 88776655H
求 SUM DD ?
解法1: 用直接寻址, "单字节加法"(共需 12 条指令)
MOV AL, BYTE PTR FIRST ; 44H → AL
ADD AL, BYTE PTR SECOND ; 55H + 44H = 99H → AL 这里用的是 ADD
MOV BYTE PTR SUM, AL ; AL(99H) → SUM
MOV AL, BYTE PTR FIRST+1 ; 33H → AL
ADC AL, BYTE PTR SECOND+1 ; 66H + 33H = 99H → AL 这里用的是 ADC
MOV BYTE PTR SUM+1, AL ; AL(99H) → SUM+1
MOV AL, BYTE PTR FIRST+2 ; 同上...
ADC AL, BYTE PTR SECOND+2
MOV BYTE PTR SUM+2, AL
MOV AL, BYTE PTR FIRST+3
ADC AL, BYTE PTR SECOND+3
MOV BYTE PTR SUM+3, AL
解法2: 用直接寻址, "双字节加法"(只需 6 条指令)
MOV AX, WORD PTR FIRST ; 3344H → AX
ADD AX, WORD PTR SECOND ; 6655H + 3344H = 9999H → AX 这里用的是 ADD
MOV WORD PTR SUM, AX ; AX(9999H) → SUM
MOV AX, WORD PTR FIRST+2
ADC AX, WORD PTR SECOND+2 ; 这里用的是 ADC
MOV WORD PTR SUM+2, AX
解法3: 用直接寻址, "双字加法"(只需 3 条指令)
MOV EAX, FIRST
ADD EAX, SECOND
MOV SUM, EAX
2.5 二进制数加1 INC
● 格式:INC 目标操作数
● 功能:目 + 1 → 目
2.6 二进制数减1 DEC
● 格式:DEC 目标操作数
● 功能:目 - 1 → 目
2.7 二进制数求补 NEG
● 格式:NEG 目标操作数
● 功能:0 - 目 → 目
"以上 3 种算术运算的一般格式有:"
INC/DEC/NEG R/M
◆ 说明:
① 对于非直接寻址的内存操作数,要用 PTR 明确说明属性。
② INC 和 DEC 影响 A、O、P、S、Z 标志,但不影响 C 标志。
③ NEG 影响 A、C、O、P、S、Z 标志。
"假设数据段为:"
N DB 0FFH, 0FFH , 0 , 0
代码段(不是连续地执行, 每条独立地运行):
MOV BX , OFFSET N
INC [BX] ; (×)
INC BYTE PTR [BX] ; (√) N单元为 0
INC WORD PTR [BX] ; (√) N、N+1 单元都为 0
INC DWORD PTR [BX] ; (√) N、N+1 单元都为 0 ;且 N+2、N+3 单元为 1
"NEG的应用:求出目标操作数的负值"
MOV AH ,5
MOV AL ,-6
NEG AH ;AH = -5
NEG AL ;AL = 6
2.8 比较指令 CMP
● 格式:CMP 目标操作数, 源操作数
● 功能:执行 “目 - 源” ,其将会产生 A、C、O、P、S、Z 一共 6 个状态标志位。该指令一般后跟条件转移指令。(6 个状态标志位能判断出 目 和 源 的相对大小)
"这种算术运算的一般格式有:"
CMP R/M, 与目标等长的R/M
CMP R/M, 不超过目标长的立即数
◆ 说明:
① CMP 指令不会改变源、目
① 源、目操作数不能同为 M 。
② 操作数中出现内存操作数时,请注意是否需要使用 PTR 运算符。
"举例:"
CMP [BX], 12 ; (×) 违反了双操作数的规则(3)
CMP BYTE PTR [BX], 12 ; (√)
CMP WORD PTR [BX], 12 ; (√) 立即数扩展了
2.9 无符号、有符号二进制数乘法 MUL、IMUL
● 格式:MUL 乘数
● 功能:见下表
● 格式:MUL 乘数
● 功能:见下表
◆ 说明:
① MUL默认乘数、被乘数、乘积为无符号二进数
② IMUL默认乘数、被乘数、乘积为有符号二进数
③ 高位积为0,则C标、O标=0,否则为1
④ 一般乘数和被乘数等长,乘积为双倍长
● 针对与 IMUL(有符号乘法),还存在两种用法如下:
① 双操作数乘法格式:IMUL 目,源操作数
功能:源 × 目 → 目
说明:源、目不能同为 M
② IMUL 目,源,立即数
功能:源 × 立即数 → 目
说明:目是 R16/R32 , 源是与目等长的 R/M,N 不能超过目长。
◆ 举个例子:实现 150000×12
"解法1:用无符号乘法格式"
MOV EAX, 150000
MOV EBX, 12
MUL EBX ; 150000 × 12 → EAX
"解法2:用有符号乘法格式1"
MOV EAX, 150000 ; ∵ EAX 最高位必为 0
MOV EBX, 12 ; 故 EBX 最高位也是 0
IMUL EBX ; 可以用有符号乘法指令
"解法3:用有符号乘法格式2"
MOV EAX, 150000
MOV EBX, 12
IMUL EAX, EBX
"解法4:用有符号乘法格式3"
MOV EAX, 150000
IMUL EBX, EAX, 12 ; 12 × EAX → EBX
2.10 无符号、有符号二进制数除法 DIV、IDIV
● 格式:DIV 除数
● 功能:见下表
● 格式:DIV 除数
● 功能:见下表
◆ 说明:
① DIV 默认 除数、被除数、商、余数 均为无符号数;IDIV 默认 除数、被除数、商、余数 均为有符号数。
② 被除数应为除数的双倍长。
③ 如除数太小,使商超出范围,则引发CPU中断 ,具体处理方法由操作系统决定,若为 DOS 操作系统,则显示:“ Divided overflow ” 后返回 DOS。
2.11 BCD码调整指令
(1) 基本概念:
假设 N = 01101001
若 N 是二进数,则N=(105)10
若 N 是 BCD 码数,则N = (69)10
由此可见,一串0、1代码,是二进数还是 BCD 码数,是由程序员定义的,CPU 并不理解。
指令格式中怎样表述 BCD 码数(在内存区如何定义 BCD 码数)呢?答案是用以下两种形式。
● 组合/未组合 BCD 码数(即压缩/未压缩 BCD 码数)的表示形式如下:
◆ 说明:
① 组合 BCD 码:一字节中含有 2 位 BCD 码
② 未组合 BCD 码:一字节中含有 1 位 BCD 码(高 4 位全为 0)
"举例,对于 69 来说, BCD 码数应写成 69H"
MOV AL, 69H ; 则 AL = 0110,1001 (组合 BCD 码)
MOV AL, 09H ; 则 AL = 0000,1001 (未组合 BCD 码)
MOV AH, 06H ; 则 AH = 0000,0110 (未组合 BCD 码)
MOV AL, 69 ; 则 AL = 0100,0101 (普通的十进制数)
"如果 69 的 BCD 码数定义在内存单元时"
BUF DB 69H ; (组合 BCD 码)
BUF DB 09H, 06H ; (未组合 BCD 码, 个位在前, 十位在后)
BUF DB 69 ; (普通的十进制数)
● BCD 码数的加减运算:
① BCD 码数是用 4 位二进数代表 1 位十进数
② 其运算法则应是:“逢十进一,减一当十”
假设:N1 = (0110 , 1001)BCD,N2 = (0000 , 1001)BCD
则:N1 + N2 = 69 + 09 = 78,即结果应当等于(0111 , 1000)BCD ,但实际结果得到的是(0111 , 0010)BCD 。
这是因为指令系统中没有实现 BCD 码数加法的指令,只能借用 ADD、ADC 指令。但是 ADD、ADC 指令默认操作数是二进数,其运算法则是“逢二进一”, 而 BCD 码数加法要求按 “逢十进一” 运算。
因此借用 ADD、ADC 指令进行 BCD 码数的加法还必须对结果进行修正,修正后的结果才是 BCD 码数的和数。
◆ 说明:事实上,N1 和 N2 可以是任意的 BCD 码数,借用 ADD、ADC 运算后必须具体分析运算结果,然后根据不同的情况选择加 06H 修正,或是加 60H 修正,或是加 66H 修正。
如果对于每一次 BCD 码数的加法都要由程序员来判断结果的话,这太麻烦了,因此指令系统中设计了一条 “组合BCD码数加法调整指令——DAA” 由硬件进行分析,再对结果进行调整。
"上例编程时只需要按以下方式编写即可"
MOV AL ,69H
ADD AL ,09H
DAA
(2)组合 BCD 码加法调整指令:DAA
● 功能:默认操作对象为 AL,并且根据具体情况对 AL 中的高/低 4 位进行修正。
● 应用场景:紧跟在以 AL 为目标寄存器的 ADD/ADC 之后,但 AL 中必须是组合 BCD 码数之和。
"例:计算 1234 + 5678 = ?"
数据段:
N1 DW 1234H
N2 DW 5678H
代码段:
MOV AL, BYTE PTR N1 ; 因为 DAA 只能针对字节进行处理,所以 N1 “用” 字节属性
ADD AL, BYTE PTR N2 ; ADD
DAA ; 组合 BCD 码加法调整
MOV BYTE PTR SUM, AL
MOV AL, BYTE PTR N1+1
ADC AL, BYTE PTR N2+1 ; ADC
DAA ; 组合 BCD 码加法调整
MOV BYTE PTR SUM+1 , AL
3、转移和调用指令
● 按照转移条件分类:无条件转移和有条件转移
● 按照转移范围分类:段内转移和段间转移
● 按照获取转移地址的方法分:直接转移和间接转移
3.1 无条件转移 JMP
● 功能:无条件转移,执行指定标号处的指令
"举例:"
MOV AH, (2*3+4)/10
JMP next ; 当运行到这条指令后,会直接跳到标号为 “next” 的语句继续执行
MOV AL, 5*8 ; 会被跳过
next: MOV AX, BX
◆ 说明:【JMP 在高级语言中相当于 C 语言的 “go to”】
① 标号是转移地址的标号。
② SHORT 是短转移符号,加了之后表示其转移范围相对于指令地址而言在 -126 ~ +129个单元之间。【这条是为了后面方便编程】
③ 段内 “JMP 标号”,在实模式下,可转移到 64K 代码段的任何位置。
④ “JMP 寄存器操作数/内存操作数” 的应用在后面的程序设计中再介绍。
3.2 有条件转移【通过状态位来判断】
● 一般格式:操作码助记符,转移地址标号
● 首先说明,所有有条件转移指令要紧接着在 CMP 指令(或其他一些指令之后,后面补充)之后。
● 应用:CMP 目,源【当 CMP 比较结果为负值,就进行转移,否则就不转移】
● 转移范围 : 代码段的任何位置
(1)按标志位的当前状态转移:只有 A、C、O、P、S、Z 中后面 5 个标志位都能够作为转移条件。
"设转移地址标号为 XYZ, 那么:【注:在编程时,关于条件转移指令里有一个小 BUG , 到时细说】"
"再次说明,条件转移指令一定要紧接着在 CMP 指令之后。"
CMP 目, 源
JC XYZ ; 若当前 C 标志为 1 时, 则进行转移, 否则就直接往下一条语句执行(不转移)
JNC XYZ ; 若当前 C 标志为 0 时, 则进行转移 【注:这里的 “N” 是 “Not” 的缩写】
JO XYZ ; 若当前 O 标志为 1 时, 则进行转移
JNO XYZ ; 若当前 O 标志为 0 时, 则进行转移
JP XYZ ; 若当前 P 标志为 1 时, 则进行转移
JNP XYZ ; 若当前 P 标志为 0 时, 则进行转移
JS XYZ ; 若当前 S 标志为 1 时, 则进行转移
JNS XYZ ; 若当前 S 标志为 0 时, 则进行转移
JZ XYZ ; 若当前 Z 标志为 1 时, 则进行转移
JNZ XYZ ; 若当前 Z 标志为 0 时, 则进行转移
(2)无符号数条件转移:只有 A、C、O、P、S、Z 中前 2 个标志位都能够作为转移条件。
"设:转移地址标号为 XYZ"
CMP N1, N2 ; N1, N2 为无符号数
JA XYZ ; 当 N1 > N2 时进行转移, 否则就直接往下一条语句执行(不转移)
JNA XYZ ; 当 N1 ≤ N2 时进行转移
JC XYZ ; 当 N1 < N2 时进行转移
JNC XYZ ; 当 N1 ≥ N2 时进行转移
(3)有符号数条件转移:
"设:转移地址标号为 Next"
CMP N1,N2 ; N1, N2 为有符号数(机器数)
JG Next ; 被减数的真值大于减数的真值时, 进行转移, 否则就直接往下一条语句执行(不转移)
JGE Next ; 被减数的真值大于等于减数的真值时, 进行转移
JL Next ; 被减数的真值小于减数的真值时, 进行转移
JLE Next ; 被减数的真值小于等于减数的真值时, 进行转移
(4)循环控制转移
MOV CX, 5
next: ... ; 会循环 5 次
...
...
LOOP next ; CX - 1 → CX, 结果不为 0 时, 进行转移
"关于上面的方案实现,也有以下这种不需要 LOOP 的方案:"
MOV CX, 5
next: ... ; 会循环 5 次
...
...
DEC CX
JNZ next ; 若当前 Z 标志为 1 时, 则进行转移(即再次循环), 否则就跳出循环(不转移)
◆ 注意:当进行循环控制转移时,其转移范围相对于指令地址而言为::126 ~+129
◆ 举一个综合的例子:某班级 40 人,某课程考试成绩存放在 SCORE 开始的内存单元里面。请统计及格人数 → OK 单元。
思路:考试成绩应视为无符号数,把成绩依次取出和 60 比较,大于等于 60 为及格。流程图如下:
"代码片段(不是完整的):"
MOV AX, SEG SCORE ; 获取段基址
MOV DS, AX
MOV DL, 0
MOV CX, 40 ; 设置循环次数(40)
MOV BX, OFFSET SCORE ; 获取偏移地址
LAST: CMP BYTE PTR [BX], 60 ; PTR双操作数的规则第(2)条
JC NO ; 如果返回 1(即分数 < 60) 则进行跳转到 NO 语句,
INC DL
NO: INC BX
DEC CX ; 循环次数减 1
JNZ LAST ; 如果 CX 不为 0 , 则进行跳转
MOV OK, DL ; 答案写入
3.3 调用与返回指令 CALL、RET、PROC
● 调用( CALL ):调用子程序,即无条件转到子程序的第一条指令
● 返回( RET):返回断点,即返回到 CALL 的后继指令
● 子程序( PROC ):能完成一定功能的相对独立的程序段
(1)汇编语言的子程序(也称为过程)定义语句
"这系列指令一般格式有:"
过程名 PROC 属性
子程序实体:
...
...
RET
过程名 ENDP
◆ 说明:
① 过程名,即子程序名,以字母开头,长度 ≤ 31
② 经汇编之后,过程名就是子程序第一条指令的地址
③ PROC/ENDP 是子程序的定界语句
④ 属性有 2 种描述:一种是 NEAR(或缺省) 代表近过程,即该子程序和调用它的那条指令在同一个代码段;第二种是 FAR 代表远过程,即该子程序和调用它的那条指令不在同一个代码段。
⑤ RET 是子程序返回指令
(2)段内调用:分为两种
段内直接调用: CALL 过程名
段内间接调用: CALL 寄存器操作数、CALL 内存操作数
● 功能:断口偏移地址 → 堆栈【注:因为在段内,所以不用传段基址,只用传偏移地址,一个地址 16 位,两个字节】
伪程序:(SP) ← (SP) - 2,然后 [CS: IP] ← (IP)
当子程序入口的偏移地址 → IP后,即可转向子程序开始执行。
(3)段间调用:也分为两种
段间直接调用:CALL 过程名
段间间接调用:CALL 内存操作数
● 功能:断口的 “段基址:偏移地址” → 堆栈【注:因为在段间,所以需要传段基址和偏移地址】
伪程序:(SP) ← (SP) - 2,[ CS:IP ] ← (CS),然后 (SP) ← (SP)-2,[ CS:IP] ← (IP)
当子程序入口的 “段基址:偏移地址” → CS:IP后,即可转向子程序实现段间转移,并去执行子程序。
(4)段内/段间返回指令 RET
● 功能一:有 NEAR 属性【即在段内】的 RET 指令,从栈顶弹出 2 字节 → IP
伪程序:(IP) ← [CS:IP] (SP) ← (SP) + 2
● 功能二:有 FAR 属性的 RET指令,从栈顶弹出 4 字节 → IP,CS
伪程序:(IP) ← [CS:IP] (SP) ← (SP) + 2,然后(CS) ← [CS:IP] (SP) ← (SP) + 2
注:如果栈顶是断口地址,则能返回断点,否则不能
"例:把 5 个 8 位二进制数转换成十进制数 → 并在屏幕上显示"
数据段:
NUM DB ××, ××, ××, ××, ××
POINTER DW N2_10 ; 汇编后, 变量 POINTER 存放了 N2_10 的偏移地址【用于①和②】
代码段:
MOV AX, SEG NUM
MOV DS, AX ; NUM 的段地址放入 AX
MOV BX, OFFSET NUM ; NUM 的偏移地址放入 BX
MOV CX, 5 ; 设置循环次数(5)
LAST: MOV DL, [BX] ; 将 [BX] 里的数据取出来放到 DL 里面去【这里类似于“C语言的函数传参”】
CALL N2_10 ; 执行子程序,备注:该指令也可以使用下注① 或②
BREAK : INC BX ;BREAK为断点
LOOP LAST
返回DOS ; 这一句是伪代码
N2_10 PROC ; 这一段是伪代码
把DL中的二进制数 → 十进制数 → 十进制数ASCⅡ码 → 屏显
RET
N2_10 ENDP
① CALL POINTER ; N2_10 的偏移地址 → IP【调用子程序的时候也能够直接用 POINTER 】
② MOV SI, POINTER ; ①也可以换成②这样
② CALL SI ; SI → IP
3.4 中断调用指令与中断返回指令 INT、IRET
● 在后面的《汇编原理——功能调用》章节会细讲,这里只略讲一下。
(1)中断调用:INT N (N=0 ~ 255)
● 功能:标志寄存器 → 堆栈
“ INT N ” 的后继指令地址 “CS:IP” → 堆栈(无条件转向 N型中断服务程序)
(2)中断返回:IRET
● 功能:从栈顶依次弹出 6 个元素 → IP,CS,标志寄存器
4、逻辑运算指令
● 首先说明:以下 5 条指令都会影响 P、S、Z 这 3 个标志位,且 C=0、O=0。
4.1 取反指令 NOT
● 格式:NOT 目
● 功能:将目取反,结果放入目中
例:将 AH(10101010) 寄存器的所有位取反。
NOT AH ; NOT 用于使所有位取反(10101010 → 01010101)
4.2 与指令 AND
● 格式: AND 目,源
● 功能:源 ∧ 目→ 目
例:将AL寄存器的低 4 位重置为 0, 其余位保持不变。
【AND 通常用于使某些位重置 0, 其它位不变的情况。】
AND AL, 11110000B ; (AL)10101010 ∧ 11110000 = 10100000
4.3 或指令 OR
● 格式:OR 目,源
● 功能:源 ∨ 目 → 目
4.4 异或 XOR
● 格式:XOR 目,源
● 功能:源⊕目→ 目
例:将 BX 寄存器的最高两位和最低两位取反, 其余位保持不变。
【XOR 通常用于使某些位取反, 其它位不变的情况。】
XOR BX, 0C003H ; (BX)1010101010101010 ⊕ 1100000000000011 = 0110101010101001
4.5 测试 TEST
● 格式:TEST 目,源
● 功能:源 ∧ 目
TEST 通常用于检测操作数某些位是 1 还是 0,希望改变原来操作数的情况,该指令后通常带有条件转移指令。
◆ 例:检测AL中的 D4是否为 1,若为 1 则进行转移。
"一般格式:"
TEST R/M, 与目等长的 R/M/N
针对该例子的代码:【这个指令在处理端口时用得多】
TEST AL, 10H
JNZ Nex
Next: MOV BL, 0
4.6 开环移位指令 SAL、SAR、SHL、SHR
● 类似于 C 语言的左右移位的运算符 “>>、<<”等【移位运算常用于一些机械控制系统的标志设置与检测】
① 算术左移: SAL 操作数,移位次数
② 算术右移: SAR 操作数,移位次数
③ 逻辑左移: SHL 操作数,移位次数
④ 逻辑右移: SHR 操作数,移位次数
◆ 说明:以上指令的操作数为 R/M,移位次数可以是立即数或 CL 。
● 图像可视图:
◆ 说明:
① 箭头往左就是左移,往右就是右移。
② 算术左移(SAL) 和 逻辑左移(SHL) 是一样的:所有位数向左移,最右边补 0 进来。
③ 逻辑右移(SHR) :所有位数向右移,最左边补 0 进来。
④ 算术右移(SAR) :所有位数向右移,最左边补 符号位 进来。
"例:AL = 1110,0010, CF = 0, 分别执行以下语句: "
SAL(SHL) AL, 1 ; 执行结果:AL = 1100,0100, CF = 1
SHR AL, 1 ; 执行结果:AL = 0111,0001, CF = 0
SAR AL, 1 ; 执行结果:AL = 1111,0001, CF = 0
"补充一:开环移位运算还能用于一些乘法"
SAL BL,1 ; 将BL中的无符号数 × 2, 当 C 标 = 1, 有溢出
SAR BL,1 ; 将BL中的有符号数 ÷ 2 取整
"补充二:将 AL 中无符号数 × 8, 有溢出则转给 OVER, 哪一种解法正确?"
解法1: 【这种解法偶尔有 BUG, 需要我们自己思考出来 —— 提示:“有条件指令”】
SAL AL, 3
JC OVER
解法2: 【这种解法没有 BUG】
MOV AH, 8
MUL AH
CMP AH, 0
JNZ OVER ; 或 JO OVER
"补充三:将 F0H 除以 2。"
带符号的情况:
MOV AL, F0H ; 1111,0000 → AL, AL = (-10H) = -16 【注:在计算机中, 负数一般用补码, 详见文章《计算机和汇编原理①》】
SAR AL, 1 ; AL 进行算术左移 → 1111,1000, 即 = (-8H)= -8
无符号的情况:
MOV AL, F0H ; 1111,0000 → AL, AL = 240
SHR AL, 1 ; AL 进行算术右移 → 0111,1000, 即 = (78H)= 120
◆ 说明:【移位指令的作用: 移位指令通常用来做 乘2 或 除2 的操作】
① 左移一位 操作数 × 2
② 右移一位 操作数 ÷ 2
③ 算术移位指令适用于带符号数的运算
④ 逻辑移位指令适用于无符号数的运算
4.7 闭环移位指令 RCL、RCR、ROL、ROR
● 类似于 C 语言的左右移位的运算符 “>>、<<”等【移位运算常用于一些机械控制系统的标志设置与检测】
① 含进位的循环左移: RCL 操作数,移位次数
② 含进位的循环右移: RCR 操作数,移位次数
③ 不含进位的循环左移:ROL 操作数,移位次数
④ 不含进位的循环右移:ROR 操作数,移位次数
◆ 说明:以上指令的操作数为 R/M, 移位次数可以是立即数或 CL 。
① 闭环移位比开环移位就多了一个 “环路” 。
② 含进位的循环左移(RCL) :所有位数向左移,C 标志补到最右边去,最高位(最左边)再补到 C 标志去。
③ 不含进位的循环左移(ROL) :所有位数向左移,最高位补到最右边去,最高位(最左边)再补到 C 标志去。
④ RCR、ROR 同理。
例:AL = 1110,0010, CF = 0, 分别执行以下语句:
RCL AL, 1 ; 执行结果:AL = 1100,0100, CF = 1
RCR AL, 1 ; 执行结果:AL = 0111,0001, CF = 0
ROL AL, 1 ; 执行结果:AL = 1100,0101, CF = 1
ROR AL, 1 ; 执行结果:AL = 0111,0001, CF = 0
"补充一:闭环环移位运算还能用于一些乘法:"
MOV CL, 5
ROL BL, CL ; 将 BL 中的无符号数 × 32, 等价于 ROL BL, 5
5、串操作指令
● 串(内存串)的定义:内存中若干连续的变量(字节、字或双字)
● 首先来看一个例子:把数据段 SOURCE 开始的若干字节, 依次传送到 ES 附加段 BUF 开始的缓冲区。【该例子并没有配合使用串操作指令来完成的】
● 思路及流程图如下:
● 代码如下:
"数据段:"
SOURCE DB ×× …… ××
LLL EQU $-SOURCE ; 计算出串内的单元个数
"附加段:"
BUF DB LLL DUP(?) ; 表示定义 LLL 个随机数(其实是预留的“空串”)
"代码段:"
先给 DS 赋初值
再给 ES 赋初值
MOV SI, OFFSET SOURCE
MOV DI, OFFSET BUF
MOV CX, LLL ; 设置循环次数 CX 为 LLL 次
AGA: MOV AL, [SI]
MOV ES:[DI], AL
INC SI
INC DI
LOOP AGA
MOV AH, 4CH ; 后续指令
INT 21H
● 我们能否用 串操作指令 来简化上述对串上的操作代码呢?答案是 yes 。
关于串操作指令的总说明:【80X86 有 6 条串操作指令:串传送、串比较、串搜索、串装入、串存储和I/O串操作,这里仅讲解前 5 条】
① 源串要放在数据段
② 目标串要放在 ES 附加段
③ 在 16 位寻址操作情况下,CPU 自动用 SI 间址访问数据段、用 DI 间址访问 ES 附加段、用 CX 做为串计数器。
④ 在 32 位寻址操作情况下,CPU 自动用 ESI 间址访问数据段、用 EDI 间址访问 ES 附加段、用 ECX 做串计数器。
⑤ 由于实模式下逻辑段的最大体积为 64K,没有必要使用 32 位寻址。故为了描述方便,在介绍指令功能时,均以 16 位寻址为基础。
5.1 串传送指令 MOVSB、MOVSW、MOVSD
● 功能:把 DS:[SI] 的若干元素 → ES:[DI] 的若干单元 【类似于 MOV 指令】
"基本型格式:"
字节串传送 MOVSB
字串传送 MOVSW
双字串传送 MOVSD
◆ 说明:
① 关于 “元素” 的概念:在字节、字、双字串传送指令中,一个元素分别是 1、2、4 个字节。
② 指令执行前的准备工作:先做 “源串的首地址/末地址 → DS:SI”,再做 “目串的首地址/末地址 → ES:DI”,然后做 “D 标志置0/置1”。
③该指令传送一个元素后,CPU自动修改 SI和DI,且当 D 标志为 0 时,SI和DI 进行增量修改(也就是,SI和DI两个 “指针” 每进行一次操作后,就一起向下移动一位);当 D 标志为 1 时,SI和DI 进行减量修改。
● 对于以上的方式,每次只能传一个元素,而加上 “REP” 后,每次就能传多个元素:
"有重复前缀的格式:"
REP MOVSB
REP MOVSW
REP MOVSD
● 准备工作: 先做 “源串的首地址/末地址 → DS:SI”,再做 “目串的首地址/末地址 → ES:DI”,然后做 “D 标志置0/置1”,最后做 “欲传送的元素个数 → CX”。
● 思路及流程图如下:
◆ 以上例题,若使用 “MOVSB”,代码段可以改写如右下:
"原来的代码段:" "分割线" "修改后的代码"
给 DS 赋初值 | 给 DS 赋初值
给 ES 赋初值 | 给 ES 赋初值
MOV SI, OFFSET SOURCE | MOV SI, OFFSET SOURCE
MOV DI, OFFSET BUF | MOV DI, OFFSET BUF
MOV CX, LLL | MOV CX, LLL
|
AGA: MOV AL,[SI] | CLD ; CLD 指令可以使 D 标志置 0; STD 指令可以使 D 标志置 1
MOV ES:[DI], AL | AGA: MOVSB
INC SI | LOOP AGA
INC DI |
LOOP AGA |
|
MOV AH, 4CH | MOV AH, 4CH
INT 21H | INT 21H
◆ 以上例题,若使用 “REP MOVSB”,代码段可以简化如右下:
"原来的代码段:" "分割线" "修改后的代码"
给 DS 赋初值 | 给 DS 赋初值
给 ES 赋初值 | 给 ES 赋初值
MOV SI, OFFSET SOURCE | MOV SI, OFFSET SOURCE
MOV DI, OFFSET BUF | MOV DI, OFFSET BUF
MOV CX, LLL | MOV CX, LLL
|
AGA: MOV AL,[SI] | CLD
MOV ES:[DI], AL | REP MOVSB
INC SI |
INC DI |
LOOP AGA |
|
MOV AH, 4CH | MOV AH, 4CH
INT 21H | INT 21H
5.2 串装入指令 LODSB、LODSW、LODSD
● 使用串装入指令的准备工作:先做 “串首址/末址 → DS:SI”,再做 “0/1 → D标”。
① LODSB ; DS:[SI] 的 1 个字节 → AL,然后自动修改 SI【会有方向】
② LODSW ; DS:[SI] 的 2 个字节 → AX,然后自动修改 SI【会有方向】
③ LODSD ; DS:[SI] 的 4 个字节 → EAX,然后自动修改 SI【会有方向】
5.3 串存储指令 STOSB、SODSW、SODSD
● 使用串装入指令的准备工作:先做 “目标区首址/末址 → ES:DI”,再做 “0/1 → D标”。
① STOSB ;AL → ES:[DI] 的 1 个单元,然后自动修改 DI【会有方向】
② STOSW ;AX → ES:[DI] 的 2 个单元,然后自动修改 DI【会有方向】
③ STOSD ;EAX → ES:[DI] 的 4 个单元,然后自动修改 DI【会有方向】
◆ 重点说明:串操作用的寄存器都是规定的,所以当串操作用了这些寄存器时,就要尽量不要让其他指令使用它们。因为在汇编程序设计语言里面,寄存器的资源是比较紧张的,只有那么些通用寄存器,而且这些通用寄存器各有各的作用,有的通用寄存器被占用过后就不能拿来作为其它东西来用。
● 对于以上的方式,每次只能传一个元素,而加上 “REP” 后,每次就能传多个元素:
"有重复前缀的格式:"
REP STOSB
REP STOSW
REP STOSD
● 准备工作:先做 “目标区首址/末址 → ES:DI”,再做 “0/1 → D标”,再做 “欲传送的元素个数 → CX”。
◆ 针对于传传送那个例子,使用 LODSB、STOSB,上述例题的代码段还可以改写成右下:
"原来的代码段:" "分割线" "修改后的代码"
给 DS 赋初值 | 给 DS 赋初值
给 ES 赋初值 | 给 ES 赋初值
MOV SI, OFFSET SOURCE | MOV SI, OFFSET SOURCE
MOV DI, OFFSET BUF | MOV DI, OFFSET BUF
MOV CX, LLL | MOV CX, LLL
|
AGA: MOV AL,[SI] | CLD
MOV ES:[DI], AL | AGA: LODSB ; 源 → AL
INC SI | STOSB ; AL → 目
INC DI | LOOP AGA
LOOP AGA |
|
MOV AH, 4CH | MOV AH, 4CH
INT 21H | INT 21H
5.4 串比较指令 CMPSB、CMPSW、CMPSD
● 功能:比较两串字符是否相等。【每一个相对地址相同的元素都得相同才能相等】
"举例:"
THE FOX
THE FOX
"虽然这两串字符内容一样, 但两串字符不相等。"
● 准备工作:先做 “源串首址/末址 → DS:SI ”,再做 “目串首址/末址→ES:DI” ,最后做 “0/1 →D标”。
"①基本型格式"
字节串比较 CMPSB
字串比较 CMPSW
双字比较 CMPSD
● 功能示意图:
● 对于以上的方式,加上 “REPE” 前缀后就是 “相等比较”,而加上 “REPNE” 前缀后就是“不等比较”:
"②有重复前缀的格式1"【常用】
REPE CMPSB
REPE CMPSW
REPE CMPSD
"③有重复前缀的格式2"
REPNE CMPSB
REPNE CMPSW
REPNE CMPSD
● 准备工作:先做 “源串首址/末址 → DS:SI ”,再做 “目串首址/末址→ES:DI” ,然后做 “0/1 →D标”,最后做 “串元素的个数 → CX”。
● 功能示意图:
◆ 补充说明:Z 标志在具有 REPE/REPNE 的串指令中的实际作用——通过 Z 的取值(Z=0或Z=1)可以判断重复进行的串指令是否正常退出,以便进行下一步处理。
"例:比较两串字符,若相等则置 FLAG 单元为 ‘Y’,不等则置为 ‘N’ "
STRING1 DB ××, …, ×× ; (源)数据段的相关操作
L1 EQU $-STRING1
FLAG DB 'Y' ; FLAG 默认初始值为 'Y'
STRING2 DB ××, …, ×× ; (目)附加段的相关操作
L2 EQU $-STRING2
先对 DS, ES 进行初始化
MOV CX, L1
CMP CX, L2 ; 先比较长度, 长度都不等那肯定两串不等
JNE NO ; 如果不等, 就跳转到 NO 语句
MOV SI,OFFSET STRING1
MOV DI,OFFSET STRING2
CLD ; D 标志置为 0, 表示增址型
REPE CMPSB ; 如果全等, 则 Z = 1, 否则 Z = 0
JZ EXIT
NO: MOV FLAG, 'N'
EXIT: MOV AH, 4CH ; 返回 DOS 的相关指令
INT 21H ; 返回 DOS 的相关指令
5.5 串搜索指令 SCASB、SCASW、SCASD
● 功能:在 ES:[DI] 的目标区,搜索是否有规定的 “关键字”
● 准备工作:先做 “目标区首址/未址 → ES:DI”,再做 “0/1 →D标”,最后做 “关键字 → AL/AX/EAX”。
"①基本型格式"
字节串搜索 SCASB
字串搜索 SCASW
双字搜索 SCASD
● 功能示意图:
● 对于以上的方式,加上 “REPE” 前缀后就是 “相等搜索”,而加上 “REPNE” 前缀后就是“不等搜索”:
"②有重复前缀的格式1"
REPE SCASB
REPE SCASW
REPE SCASD
"③有重复前缀的格式2"【常用】
REPNE SCASB
REPNE SCASW
REPNE SCASD
● 准备工作:先做 “目标区首址/未址 → ES:DI”,再做 “0/1 →D标”,最后做 “关键字 → AL/AX/EAX”,最后做 “目标串元素的个数 → CX”。
● 功能示意图:
6、处理机控制指令
6.1 关于 C 标志的指令 CLC、STC、CMC
● C标置0:CLC
● C标置1:STC
● C标取反:CMC
◆ 使用情况:当我们想要在做某一运算之前,将 C 标志清零。
6.2 关于 D 标志的指令 CLD、STD、CMD
● D标置0:CLD
● D标置1:STD
◆ 使用情况:串操作指令的方向标志, D = 0 代表增址型,D = 1 代表减址型。
6.3 关于 I 标志的指令 CLI、STI、CMI
● I标置0:CLI
● I标置1:STI
◆ 使用情况:中断的一个开关时使用。
6.4 其他指令 NOP、HTL
● 空操作:NOP
● 暂停:HLT
◆ 使用情况:NOP 表示啥也不做,空耗一个周期【填充,延时】。
六、参考附录:
[1] 《微型计算机原理与接口技术(慕课板)》
清华大学出版社
[2] 《汇编语言程序设计(第2版)》
[3] 《通用寄存器介绍和段寄存器的介绍》
链接: https://blog.csdn.net/thanklife/article/details/11229093.
上一篇文章链接: 【计算机原理和汇编原理④】——指令系统(上).
下一篇文章链接: 【计算机和汇编原理⑥】——UNIX的编程基础【SEGMENT、ASSUME、PROC、ORG、END】.
第二天写的时候,有 5 千字忘了保存,当场差点崩溃 😵
⭐️ ⭐️