【计算机与UNIX汇编原理⑤】——指令系统(下)【 2万5千字总结 】


🕐 🕑 🕒 🕓 🕔 🕕 🕖 🕗 🕘 🕙 🕚 🕛

几乎所有 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,33
        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 BUFDBTHEQUICKBROWNFOX;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 11223344		; 十进制(最好把这些十进制的数转换为十六进制)
		WBUF DW ?,?				; 双字节
		XX DB 0FFH,0
		YY DB 0FFH,0FFH,00
		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 11223344
	
	代码段为:
		MOV AL,BUF[3]			; 访问 BUF数据段的第“3”个单元(下标分别为0123), 即把 “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					; 1float属性(连续六个字节)的单元构成的数据段
	
	
	"第一个代码段:" 
		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 用于使所有位取反(1010101001010101)

4.2 与指令 AND

  ● 格式: AND 目,源
  ● 功能:源 ∧ 目→ 目

	例:将AL寄存器的低 4 位重置为 0, 其余位保持不变。
	【AND 通常用于使某些位重置 0, 其它位不变的情况。】
	AND AL, 11110000B		; (AL)1010101011110000 = 10100000

4.3 或指令 OR

  ● 格式:OR 目,源
  ● 功能:源 ∨ 目 → 目

4.4 异或 XOR

  ● 格式:XOR 目,源
  ● 功能:源⊕目→ 目

	例:将 BX 寄存器的最高两位和最低两位取反, 其余位保持不变。
	【XOR 通常用于使某些位取反, 其它位不变的情况。】
	XOR BX, 0C003H			; (BX)10101010101010101100000000000011 = 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 千字忘了保存,当场差点崩溃 😵
⭐️ ⭐️

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一支王同学

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值