========================================================================
微机原理与接口技术 · 第 5 篇
指令系统(一):数据传送与算术运算 —— 30 分钟速通
========================================================================
前置:第 4 篇(寻址方式)。这一篇开始真正"写指令"了。
这一篇只讲两件事:怎么搬数据,怎么算数。
========================================================================
全章地图:这一篇到底在讲什么
========================================================================
8086 有一百多条指令,听起来吓人。但按功能分只有五类:
这一篇先啃前两类,因为它们是"砖头":
任何程序,八成以上的指令都是"搬"和"算"。
一共六站:
第 1 站 指令长什么样:机器码的结构 (4 分钟)
第 2 站 MOV 与数据传送 (6 分钟)
第 3 站 加减法:ADD/SUB/CMP/NEG (5 分钟)
第 4 站 ★ CMP 与条件转移(有符号 vs 无符号)(6 分钟)
第 5 站 乘除法与符号扩展 (5 分钟)
第 6 站 BCD 调整与综合练习 (4 分钟)
★★ 本篇的核心数字(机器码全是按编码规则算出来的)★★
MOV AX, 1234H B8 34 12H 立即数低字节在前
MOV AX, BX 8B C3H mod=11 表示寄存器
XCHG AX, BX 93H ★ 特例:90H + 寄存器号
LEA AX, [BX+SI] 8D 00H
CMP AX, BX 3B C3H (做了减法但不存结果)
MUL BL F6 E3H F6 /4
DIV BL F6 F3H F6 /6
CBW 98H 符号扩展到 AX
CWD 99H 符号扩展到 DX:AX
========================================================================
第 1 站(0-4 分钟) 指令长什么样
------------------------------------------------------------------------
【一条指令 = 操作码 + 操作数】
MOV AX, BX
↑ ↑ ↑
操作码 目的 源
机器码:8B C3
8B ← 操作码:告诉 CPU「这是一条 MOV,方向是 reg←r/m」
C3 ← ModR/M:11 000 011
mod=11(寄存器)、reg=000(AX)、r/m=011(BX)
★ 第 4 篇讲的 ModR/M,就是"操作数在哪"的编码。
【指令长度不固定:1 ~ 6 个字节】
XCHG AX, BX 93 1 字节
INC AX 40 1 字节
MOV AX, BX 8B C3 2 字节
MOV AX, [BX] 8B 07 2 字节
MOV AX, 1234H B8 34 12 3 字节
MOV AX, [1234H] 8B 06 34 12 4 字节
MOV AX, [BX+SI+1234H] 8B 80 34 12 4 字节
★ 变长指令的好处:常用的(寄存器之间)短,少用的(复杂寻址)长,
平均下来代码更紧凑。代价是译码复杂。
【立即数和位移都是"低字节在前"】
MOV AX, 1234H → B8 34 12
↑ ↑
低 高
这就是第 3 篇讲的小端模式,指令里的数也一样。
========================================================================
第 2 站(4-10 分钟) MOV 与数据传送
------------------------------------------------------------------------
【MOV:最基础的搬运工】
MOV 目的, 源
注意方向:目的在前,源在后。和我们平时说"把源送到目的"是反的,
所以初学时容易写反。
【★ 三条铁律(考试最爱考)】
① 不能【存储器 → 存储器】
想搬?必须经过寄存器中转:
MOV AL, [SI] ; 先从内存取到 AL
MOV [DI], AL ; 再从 AL 存到内存
② 不能把立即数直接送【段寄存器】
必须经过通用寄存器:
MOV AX, 2000H
MOV DS, AX
★ 这是初学者最常犯的错:MOV DS, 2000H 是非法的。
③ 不能【段寄存器 → 段寄存器】,且 CS 永远不能当目的
CS 只能用 JMP FAR / CALL FAR / RETF / INT 改。
【MOV 的机器码家族(了解规律即可)】
88 /r MOV r/m8, r8 从寄存器存到内存/寄存器
8A /r MOV r8, r/m8 从内存/寄存器取到寄存器
89 /r MOV r/m16, r16
8B /r MOV r16, r/m16
B0+reg MOV r8, imm8 立即数送 8 位寄存器
B8+reg MOV r16, imm16 立即数送 16 位寄存器
★ 88/89 是"往外写",8A/8B 是"往里读"。
这就是为什么 MOV AX,BX 是 8B C3(读 BX)、
而 MOV BX,AX 是 8B D8(读 AX,reg=011 是 BX)。
【XCHG:交换,有个大特例】
XCHG AX, BX
机器码:93
★ 特例:XCHG AX, r16 编码成 90H + 寄存器号
(AX,AX)=90、(AX,CX)=91、(AX,DX)=92、(AX,BX)=93
为什么?因为用一个字节就够了,省空间。
(90H 单独一条 XCHG AX,AX 就是那个著名的 NOP 指令)
【LEA:只算地址,不取数据】
LEA AX, [BX+SI]
机器码:8D 00
作用:把 [BX+SI] 这个【地址】算出来,放进 AX。
它不访问存储器,只是做加法!
★ LEA 和 MOV 的区别(常考):
LEA AX, [BX] ; AX = BX(把地址值给 AX)
MOV AX, [BX] ; AX = 内存[BX] 里的内容(要去取)
LEA 的妙用:可以做三次加法(BX+SI+位移),比用 ADD 两次还快。
【PUSH / POP:压栈出栈】
PUSH AX 50H (50H + 寄存器号,AX=0)
PUSH BX 53H
POP AX 58H (58H + 寄存器号)
一次压入/弹出【两个字节】(一个字)。
★ 记住第 3 篇的:PUSH 先减 2 再存,POP 先取再加 2。
【IN / OUT:和端口打交道(第 8 篇细讲)】
IN AL, 60H ; 从 60H 端口读一个字节到 AL
OUT 60H, AL ; 把 AL 写到 60H 端口
★ 端口地址 ≤ 255 时可以直接写;> 255 必须用 DX:
MOV DX, 3F8H
IN AL, DX
★ 注意:这是 I/O 端口,不是存储器地址。8086 有两套独立的地址空间
(存储器 1MB,I/O 64KB),靠 M/IO 引脚区分。
========================================================================
第 3 站(10-15 分钟) 加减法
------------------------------------------------------------------------
【基本四条】
ADD 目的, 源 目的 = 目的 + 源
ADC 目的, 源 目的 = 目的 + 源 + CF (带进位加)
SUB 目的, 源 目的 = 目的 − 源
SBB 目的, 源 目的 = 目的 − 源 − CF (带借位减)
为什么要 ADC/SBB?为了做【多字节运算】。
例:两个 32 位数相加(8086 一次只能算 16 位)
低 16 位用 ADD(会置 CF)
高 16 位用 ADC(把低位的进位加进来)
【INC / DEC:加减 1】
INC AX 40H (40H + 寄存器号)
DEC AX 48H
★ 重要区别:INC/DEC 不影响 CF!
而 ADD AX,1 会影响 CF。
在循环里用 INC SI 而不担心破坏进位标志,就是因为这个。
【CMP:比较(本质是做减法)】
CMP AX, BX 3B C3H
★ 它做一次 AX − BX,但结果不保存,只更新标志位。
这是本章最重要的指令,因为它要和条件转移配合(下一站)。
【NEG:取负(求补)】
NEG AX F7 D8H (F7 /3)
作用:AX = 0 − AX,等价于"取反加一"。
★ 用途:把加法变减法。A − B 可以写成 A + (−B)。
另外 NEG 后 CF 的值有特殊含义:AX≠0 时 CF=1。
【标志位怎么变(四条规律)】
① ZF:结果为 0 时置 1(判断相等就靠它)
② SF:结果的最高位(就是"看起来是不是负数")
③ CF:无符号数有没有进位/借位
④ OF:有符号数有没有溢出(正+正得负 或 负+负得正)
【实算几个】
ADD AL, BL AL=05H, BL=03H → 结果 08H
标志位:全部为 0(没有进位、不是 0、非负)
ADD AL, BL AL=7FH, BL=01H → 结果 80H
标志位:AF=1、SF=1、OF=1
(127+1=128 超出 8 位有符号范围)
ADD AL, BL AL=FFH, BL=01H → 结果 00H
标志位:CF=1、PF=1、AF=1、ZF=1
(无符号进位;结果恰好为 0)
★ 对比后两个例子:都是"最高位变了",但一个 OF=1(有符号溢出),
一个 CF=1(无符号进位)。这就是 CF 和 OF 的区别。
========================================================================
第 4 站(15-21 分钟) ★ CMP 与条件转移
------------------------------------------------------------------------
这一站是汇编里用得最多、也最容易错的地方。
【CMP 的用法模式】
CMP A, B
JE 相等时去哪 ; ZF=1 说明 A=B
JG 大于时去哪 ; 有符号
JB 小于时去哪 ; 无符号
【关键洞察:有符号和无符号用不同的标志位】
有符号数比较:看 SF、OF、ZF
小于 → SF ≠ OF
大于 → SF = OF 且 ZF = 0
无符号数比较:看 CF、ZF
小于 → CF = 1
大于 → CF = 0 且 ZF = 0
★ 为什么不一样?因为"大小"的定义不同:
无符号数 0FFH 是 255(比 1 大);
有符号数 0FFH 是 −1(比 1 小)。
硬件只做一次减法,两种解释靠不同的标志位。
【实算:AL=03H, BL=07H,CMP AL,BL 之后】
AL − BL = 03H − 07H = FCH(即 −4)
ZF = 0(不是 0)
SF = 1(结果最高位是 1,看起来是负数)
OF = 0(3−7 没有溢出)
CF = 1(3 减 7 需要借位)
判断:
有符号:SF(1) ≠ OF(0) → 小于 → JL 成立 ✓(3 < 7)
无符号:CF = 1 → 小于 → JB 成立 ✓(3 < 7)
两者结论一样,但走的是不同标志位。
【再看一个会分歧的例子】
设 AL = 0FFH(无符号 255,有符号 −1)、BL = 01H
AL − BL = FFH − 01H = FEH(即 −2)
SF = 1、OF = 0、CF = 0
有符号:−1 < 1 → 小于 → JL 成立
无符号:255 > 1 → 大于 → JA 成立
★ 同一个 CMP,结论完全相反!
所以做题时必须先确定数据是有符号还是无符号。
这是本章最容易掉进去的坑。
【条件转移指令速查(常用 12 条)】
JE / JZ 相等 / 为零 ZF=1
JNE / JNZ 不等 / 不为零 ZF=0
JA / JNBE 高于(无符号) CF=0 且 ZF=0
JAE / JNB 高于等于(无符号) CF=0
JB / JNAE 低于(无符号) CF=1
JBE / JNA 低于等于(无符号) CF=1 或 ZF=1
JG / JNLE 大于(有符号) SF=OF 且 ZF=0
JGE / JNL 大于等于(有符号) SF=OF
JL / JNGE 小于(有符号) SF≠OF
JLE / JNG 小于等于(有符号) ZF=1 或 SF≠OF
★ 记忆法:A/B 是无符号(Above/Below),G/L 是有符号(Greater/Less)。
========================================================================
第 5 站(21-26 分钟) 乘除法与符号扩展
------------------------------------------------------------------------
乘除法最容易记混的是"结果放在哪"。看这张图就清楚了。
【乘法】
MUL 源 无符号乘(F6 /4 或 F7 /4)
IMUL 源 有符号乘(F6 /5 或 F7 /5)
8 位:AL × 源 → AX(AH 是高 8 位,AL 是低 8 位)
16 位:AX × 源 → DX:AX
实算:
AL=5, BL=3 → AX = 15(AH=00H, AL=0FH),CF=OF=0
AL=20, BL=13 → AX = 260(AH=01H, AL=04H),CF=OF=1
★ CF 和 OF 的含义:如果结果的高半部分(AH 或 DX)不为 0,
说明结果"放不进"半宽里,CF=OF=1。这用于判断是否需要扩宽。
【除法】
DIV 源 无符号除(F6 /6 或 F7 /6)
IDIV 源 有符号除(F6 /7 或 F7 /7)
8 位:AX ÷ 源 → AL = 商、AH = 余数
16 位:DX:AX ÷ 源 → AX = 商、DX = 余数
实算:
AX=100, BL=7 → AL=0EH(商 14)、AH=02H(余 2)
★ 两个大坑:
① 除法前必须把 DX/AH 清零(有符号要先 CWD),
否则会算出荒唐的结果。
② 如果商超过 FFH(8 位)或 FFFFH(16 位),
CPU 会产生【除法错中断(类型 0)】,程序直接崩。
例:AX=4660 ÷ 7 = 665,超过 255 → 触发类型 0 中断。
【符号扩展:CBW / CWD】
8 位负数要做 16 位运算前,必须先"补符号位"。
CBW AL → AX (98H)
CWD AX → DX:AX (99H)
CBW 实算:
AL = 05H(+5) → AX = 0005H(AH 填 00H)
AL = FBH(−5) → AX = FFFBH(AH 填 FFH)
AL = 80H(−128)→ AX = FF80H(AH 填 FFH)
★ 一句话:符号扩展 = 用符号位填高位。
补 0 会把 −5 变成 +251,这是致命的错误。
什么时候必须用?
MOV AL, BL
CBW ; ← 少了这一条,负数就全错了
MOV CL, 10
IDIV CL
标准写法(有符号除法前):
MOV AL, [SI]
CBW ; AL → AX,补好符号
MOV CL, [DI]
IDIV CL ; AX ÷ CL → AL 商、AH 余
========================================================================
第 6 站(26-30 分钟) BCD 调整与综合练习
------------------------------------------------------------------------
【BCD 调整四条】
DAA (27H) ADD 之后调整,用于加法
DAS (2FH) SUB 之后调整,用于减法
AAA (37H) ADD 之后调整,用于 ASCII 数字
AAS (3FH) SUB 之后调整
实算 BCD 加法:
25H + 37H = 5CH(低半字节 C>9,必须调整)
DAA:先加 06H → 62H(高半字节 6 ≤ 9,不用再加 60H)
结果 62H = 十进制 62 ✓(25 + 37 = 62)
实算 BCD 减法:
42H − 17H = 2BH(低半字节 2−7 需要借位,AF=1)
DAS:先减 06H → 25H
结果 25H = 十进制 25 ✓(42 − 17 = 25)
★ 用法必须是紧跟在加减指令后面,中间不能插入别的指令
(因为要看这一条产生的 AF 和 CF)。
【综合练习一:写两个 32 位数相加】
设第一个数存在 DS:SI 指向的 4 个字节,
第二个数存在 DS:DI 指向的 4 个字节。
把和存回 SI 指向的地方。
MOV AX, [SI] ; 低 16 位
ADD AX, [DI] ; 相加,可能产生进位 CF
MOV [SI], AX
MOV AX, [SI+2] ; 高 16 位
ADC AX, [DI+2] ; ★ 用 ADC 把低位的进位加进来
MOV [SI+2], AX
★ 关键就是那个 ADC。用 ADD 就会丢掉进位。
【综合练习二:判断一个数是正、负还是零】
CMP AL, 0
JE IS_ZERO ; ZF=1 → 是 0
JS IS_NEG ; SF=1 → 是负数
; 否则是正数
JMP DONE
IS_NEG:
; 负数处理
JMP DONE
IS_ZERO:
; 零处理
DONE:
★ 注意这里用的是 JS(SF=1),因为比较的对象是 0,
不需要考虑溢出问题。
【综合练习三:无符号数取绝对值】
MOV AL, [SI]
CMP AL, 80H
JB POSITIVE ; 小于 80H → 是"正数"(无符号意义上)
NEG AL ; 0 − AL,取补
POSITIVE:
MOV [DI], AL
★ 这里必须用 JB(无符号),因为我们要判断的是"最高位是不是 1"。
========================================================================
自测:12 题检验这一篇
========================================================================
1. 一条指令由哪两部分组成?指令长度为什么是变长的?
2. MOV 的三条铁律是什么?
3. 想把 [SI] 的内容搬到 [DI],怎么写?为什么不能一条指令搞定?
4. XCHG AX, BX 的机器码是多少?为什么这么短?
5. LEA AX,[BX] 和 MOV AX,[BX] 有什么区别?
6. INC 和 ADD AX,1 有什么重要区别?
7. ADC 和 SBB 是干什么用的?为什么需要它们?
8. CMP 的本质是什么?它保存运算结果吗?
9. 有符号比较看哪些标志位?无符号比较看哪些?
10. MUL BL 的结果放在哪?如果 AH≠0 会怎样?
11. DIV BL 之前为什么要把 AH 清零?不清零会有什么后果?
12. CBW 和 CWD 分别做什么?为什么负数必须用它们?
------------------------------------------------------------------------
参考答案
------------------------------------------------------------------------
1. 操作码 + 操作数。变长是为了让常用指令短、少用指令长,
平时代码更紧凑(代价是译码复杂)。
2. ① 不能存储器→存储器;② 不能立即数→段寄存器;
③ 不能段寄存器→段寄存器,且 CS 不能当目的。
3. MOV AL,[SI] 然后 MOV [DI],AL。因为 8086 不支持存储器到存储器
直接传送,必须经过寄存器中转。
4. 93H。因为 XCHG AX,r16 有专门编码:90H + 寄存器号,BX 是 3。
5. LEA 只算地址(把 BX 的值给 AX),不访问存储器;
MOV 会去 BX 指向的地址取数据。LEA 很快,常用来做加法。
6. INC 不影响 CF,ADD AX,1 会影响 CF。
循环里用 INC 不会破坏进位标志。
7. 带进位加、带借位减。用于多字节运算:低位用 ADD/SUB 产生进位,
高位用 ADC/SBB 把进位算进来。
8. 做一次减法(目的 − 源),但结果不保存,只更新标志位。
9. 有符号:SF、OF、ZF(小于看 SF≠OF)。
无符号:CF、ZF(小于看 CF=1)。
10. 8 位乘法的结果放在 AX(AH 高 8 位、AL 低 8 位)。
AH≠0 说明结果超出 8 位,此时 CF=OF=1。
11. 因为 DIV 是"AX ÷ 源",如果 AH 有垃圾值,被除数就不是你想要的那个数,
会算出错误结果或商溢出。有符号除法要先 CWD。
商超过 FFH 会触发除法错中断(类型 0)。
12. CBW:AL 符号扩展成 AX;CWD:AX 符号扩展成 DX:AX。
负数高位必须补 1,补 0 会变成很大的正数。
========================================================================
本篇最容易犯的 7 个错
========================================================================
1. MOV 目的,源 的方向写反
目的在前。MOV AX,BX 是把 BX 给 AX。
2. MOV DS, 2000H
非法!立即数不能直接进段寄存器,要经过 AX 中转。
3. 忘了用 ADC/SBB 做多字节运算
高位必须用带进位的指令,否则低位进位丢失。
4. 有符号和无符号的判断混用
有符号用 JL/JG(看 SF/OF),无符号用 JB/JA(看 CF)。
0FFH 在两个体系里大小完全相反。
5. 除法前忘了清 DX/AH 或 CWD
不清零 → 结果错;不 CWD → 负数全错。
6. 以为 INC 会影响 CF
不会。只有 ADD/SUB 系列才影响 CF。
7. BCD 调整指令中间插了别的指令
DAA/DAS 必须紧跟在 ADD/SUB 后面,因为要看那一条产生的标志位。
========================================================================
这一篇和后面的关系
========================================================================
· 第 6 篇(汇编程序):所有程序都是"搬 + 算 + 判断 + 跳",
这一篇给了"搬"和"算",下一篇给"判断"和"跳"。
· 第 7 篇(存储器):地址用 LEA 或 MOV 配合寻址方式算出。
· 第 8 篇(I/O):IN/OUT 指令在这一篇只提了一句,那里细讲。
· 第 10 篇(8255A):端口操作全靠 IN/OUT。
一句话:这一篇是"动词表"。学完你就知道 CPU 能干哪些动作了,
下一篇教你怎么把这些动作串成程序。
========================================================================
打印提示
========================================================================
单个 HTML 文件,Ctrl+P 直接打印或存 PDF。
对照表与矩阵已设置避免跨页断开,正文按 10pt 排版。