
🌈 say-fall:个人主页 🚀 专栏:《手把手教你学会C++》 | 《系统深入Linux操作系统》 | 《数据结构与算法》 | 《小游戏与项目》 💪 格言:做好你自己,才能吸引更多人,与他们共赢,这才是最好的成长方式。
📝 前言
提到汇编语言,很多人的第一反应是"太难了""看不懂""现在谁还写汇编"。确实,我们平时开发用的是C++、Java、Python这些高级语言,汇编似乎离我们很远。但是,当你想理解指针的底层原理 、想搞懂操作系统的内核是怎么写的 、想学习逆向工程和漏洞分析的时候,你会发现------汇编是绕不过去的一道坎。
8086/8088处理器的16位指令系统是整个x86架构的基石。我们现在用的Intel Core、AMD Ryzen处理器,依然可以运行几十年前8086的指令,这种向下兼容性正是x86架构能长期占据主导地位的重要原因之一。
不过不用担心------本文将从零开始,系统梳理8086指令系统的每一个知识点,配合大量代码示例和图解,带你一步步搞懂指令格式、寻址方式、六大类指令的功能与应用。
通过本文,你将掌握:
| 技能 | 应用场景 |
|---|---|
| 指令格式与操作数类型 | 理解每条指令的结构和书写规范 |
| 7种寻址方式 | 灵活访问寄存器、内存和立即数 |
| 数据传送指令 | 数据在CPU、内存和I/O端口之间流转 |
| 算术运算指令 | 加减乘除运算及多字节数处理 |
| 逻辑运算与移位指令 | 按位操作、位清零/置位/取反 |
| 串操作指令 | 高效处理字符串和数据块 |
| 程序控制指令 | 分支、循环、子程序调用和中断 |
📌 前置知识: 了解二进制/十六进制的基本概念,知道寄存器是什么,对"段地址:偏移地址"的内存模型有初步印象即可。
文章目录
-
- [📝 前言](#📝 前言)
- [一、🔧 指令系统基本概念](#一、🔧 指令系统基本概念)
- [二、🔍 Intel x86-16处理器寻址方式](#二、🔍 Intel x86-16处理器寻址方式)
-
- [1️⃣ 立即寻址](#1️⃣ 立即寻址)
- [2️⃣ 寄存器寻址](#2️⃣ 寄存器寻址)
- [3️⃣ 存储器寻址](#3️⃣ 存储器寻址)
- [4️⃣ 隐含寻址](#4️⃣ 隐含寻址)
- [📌 寻址方式总结](#📌 寻址方式总结)
- [三、🛠️ 8086指令系统详解](#三、🛠️ 8086指令系统详解)
-
- [📌 8086指令系统分类](#📌 8086指令系统分类)
- [3.1 📦 数据传送指令](#3.1 📦 数据传送指令)
-
- (1)通用数据传送指令
-
- [① 一般数据传送指令MOV](#① 一般数据传送指令MOV)
- [② 堆栈操作指令PUSH和POP](#② 堆栈操作指令PUSH和POP)
- [③ 交换指令XCHG](#③ 交换指令XCHG)
- [④ 字位扩展指令CBW和CWD](#④ 字位扩展指令CBW和CWD)
- (2)输入输出指令IN和OUT
- (3)地址传送指令
-
- [① 取偏移地址指令LEA](#① 取偏移地址指令LEA)
- [② LDS和LES指令](#② LDS和LES指令)
- (4)标志位操作指令
- [3.2 ➕ 算术运算指令](#3.2 ➕ 算术运算指令)
- [3.3 🔣 逻辑运算和移位指令](#3.3 🔣 逻辑运算和移位指令)
- [3.4 📜 串操作指令](#3.4 📜 串操作指令)
-
- (1)串操作的共同特点
- (2)重复前缀
- (3)常用串操作指令
-
- [① MOVS(串传送)](#① MOVS(串传送))
- [② CMPS(串比较)](#② CMPS(串比较))
- [③ SCAS(串扫描)](#③ SCAS(串扫描))
- [④ LODS(串装入)和 STOS(串存储)](#④ LODS(串装入)和 STOS(串存储))
- [3.5 🔀 程序控制指令](#3.5 🔀 程序控制指令)
-
- (1)转移指令
-
- [① 无条件转移指令JMP](#① 无条件转移指令JMP)
- [② 条件转移指令](#② 条件转移指令)
- (2)循环控制指令
- (3)过程调用和返回指令
-
- [① CALL(过程调用)](#① CALL(过程调用))
- [② RET(过程返回)](#② RET(过程返回))
- (4)中断指令
-
- [① INT n(中断指令)](#① INT n(中断指令))
- [② IRET(中断返回)](#② IRET(中断返回))
- [3.6 ⚙️ 处理器控制指令](#3.6 ⚙️ 处理器控制指令)
- [四、🤔 几个思考题](#四、🤔 几个思考题)
-
- [1️⃣ MOV指令有哪些限制条件?](#1️⃣ MOV指令有哪些限制条件?)
- [2️⃣ LEA和MOV有什么区别?](#2️⃣ LEA和MOV有什么区别?)
- [3️⃣ 比较两个有符号数和无符号数的大小,分别用什么条件转移指令?](#3️⃣ 比较两个有符号数和无符号数的大小,分别用什么条件转移指令?)
- [4️⃣ PUSH和POP操作数为什么必须是16位的?](#4️⃣ PUSH和POP操作数为什么必须是16位的?)
- [5️⃣ 串操作指令中DF标志位的作用是什么?](#5️⃣ 串操作指令中DF标志位的作用是什么?)
- [📚 学习总结与建议](#📚 学习总结与建议)
一、🔧 指令系统基本概念
在学习具体的指令之前,我们需要先搞清楚几个最基本的概念------这些概念是理解整个指令系统的前提。
1️⃣ 指令与指令系统
指令:控制计算机完成某种操作的命令。一条完整的指令必须包含三个核心信息:
- 运算数据的来源(源操作数)
- 运算结果的去向(目标操作数)
- 执行的具体操作(操作码)
指令系统 :处理器所能识别的所有指令的集合。不同架构的处理器有不同的指令系统,而同一系列的处理器(如Intel x86系列)通常是向下兼容的------早期处理器的指令在后期的处理器上依然可以运行。
⚠️ 指令兼容性是x86架构能长期占据市场主导地位的重要原因之一
2️⃣ 指令格式
x86-16指令的基本格式为:
assembly
操作码 [目标操作数], [源操作数]
- 操作码 :告诉CPU要执行什么操作,比如
MOV(传送)、ADD(加法)、SUB(减法)等 - 操作数:参加操作的数据或数据存放的地址
根据操作数的数量,指令可以分为以下几类:
| 分类 | 说明 | 示例 |
|---|---|---|
| 零操作数指令 | 只有操作码 | HLT(暂停)、NOP(空操作) |
| 单操作数指令 | 一个操作数 | INC AX(AX加1)、NEG BL(对BL求补) |
| 双操作数指令 | 两个操作数(最常见) | MOV AX, BX、ADD AL, 5 |
| 多操作数指令 | 三个及以上操作数 | 16位汇编中较少见 |
3️⃣ 指令中的操作数类型
操作数是指令的核心,根据其存放位置的不同,可以分为以下三种类型:
(1)立即数操作数
立即数本身就是参加操作的数据,直接包含在指令中。
- 可以是8位(00HFFH)或16位(0000HFFFFH)的无符号数或带符号数
- ⚠️ 立即数只能作为源操作数,不能作为目标操作数
assembly
MOV AX, 1234H ; 将立即数1234H传送到AX寄存器
MOV BL, 22H ; 将立即数22H传送到BL寄存器
(2)寄存器操作数
参加运算的数据存放在CPU的通用寄存器中。
- 可以是16位寄存器(AX、BX、CX、DX、SI、DI、BP、SP)或8位寄存器(AH、AL、BH、BL、CH、CL、DH、DL)
- 特点:访问速度最快,因为寄存器在CPU内部,不需要访问内存
assembly
MOV AX, BX ; 将BX寄存器的内容传送到AX寄存器
MOV DL, CH ; 将CH寄存器的内容传送到DL寄存器
(3)存储器操作数
参加运算的数据存放在内存的某个单元中。
- 表现形式:用方括号
[]括起来的偏移地址,如[1200H]、[BX] - 特点:访问速度最慢,因为需要通过总线访问内存
assembly
MOV AL, [1200H] ; 将内存偏移地址1200H单元的内容传送到AL
MOV AX, [1200H] ; 将1200H和1201H单元的内容传送到AX(低字节在低地址,高字节在高地址)
三种操作数类型对比
| 操作数类型 | 存放位置 | 访问速度 | 能否作为目标操作数 | 特点 |
|---|---|---|---|---|
| 立即数 | 指令中 | 最快 | 不能 | 直接给出数据,只能作为源 |
| 寄存器 | CPU内部 | 快 | 能 | 数量有限,访问速度最快 |
| 存储器 | 内存 | 最慢 | 能 | 容量大,访问速度慢 |
二、🔍 Intel x86-16处理器寻址方式
寻址方式就是寻找操作数所在地址的方法。简单来说,就是CPU如何找到指令要操作的数据。
在16位系统中,物理地址的计算公式为:
物理地址 = 段基地址 × 16 + 偏移地址
段基地址默认由段寄存器提供,也可以通过段重设的方式指定。
1️⃣ 立即寻址
指令中的源操作数是立即数,数据本身直接包含在指令中,仅适用于源操作数。
assembly
MOV AX, 1200H ; 立即寻址,将1200H传送到AX
2️⃣ 寄存器寻址
参加操作的操作数在CPU的通用寄存器中,执行速度最快。
assembly
MOV AX, BX ; 寄存器寻址,将BX的内容传送到AX
3️⃣ 存储器寻址
这是最复杂也是最重要的一类寻址方式。操作数存放在内存中,指令中给出的是操作数的偏移地址。
(1)直接寻址
指令中直接给出操作数的偏移地址,默认段寄存器是DS。
assembly
MOV AX, [1200H] ; 默认使用DS段,物理地址=DS×16+1200H
MOV AX, ES:[1200H] ; 段重设,使用ES段,物理地址=ES×16+1200H
(2)寄存器间接寻址
操作数的偏移地址存放在指定的寄存器中。只能使用BX、BP、SI、DI这四个寄存器作为间址寄存器。
段寄存器默认规则:
- 使用BX、SI、DI时,默认段寄存器是DS
- 使用BP时,默认段寄存器是SS(堆栈段)
assembly
MOV BX, 1200H
MOV AX, [BX] ; 偏移地址在BX中,默认DS段,物理地址=DS×16+1200H
(3)寄存器相对寻址
操作数的偏移地址 = 间址寄存器的内容 + 位移量(8位或16位常数)。
assembly
MOV BX, 1200H
MOV AL, [BX+5] ; 偏移地址=BX+5=1205H,也可以写成[BX]5
💡 寄存器相对寻址常用于访问一维数组------位移量是数组首地址,寄存器存放数组下标
(4)基址变址寻址
操作数的偏移地址 = 基址寄存器的内容 + 变址寄存器的内容。
- 基址寄存器:BX、BP;变址寄存器:SI、DI
- 段寄存器默认由基址寄存器决定:BX默认DS,BP默认SS
assembly
MOV SI, 1100H
MOV BX, 0200H
MOV AX, [BX+SI] ; 偏移地址=BX+SI=1300H,也可以写成[BX][SI]
(5)基址变址相对寻址
操作数的偏移地址 = 基址寄存器 + 变址寄存器 + 位移量。
assembly
MOV DI, 1100H
MOV BP, 0200H
MOV AL, [BP+DI+5] ; 偏移地址=BP+DI+5=1305H,也可以写成[BP][DI]5
💡 基址变址相对寻址常用于访问二维数组------位移量是数组首地址,基址存行下标,变址存列下标
4️⃣ 隐含寻址
指令中没有明确给出操作数的地址,操作数隐含在默认的寄存器中。
assembly
MUL BL ; 隐含源操作数是AL,目标操作数是AX,执行 AL × BL → AX
CBW ; 隐含操作数是AL和AX,将AL的符号位扩展到AH
📌 寻址方式总结
| 寻址方式 | 操作数位置 | 示例 |
|---|---|---|
| 立即寻址 | 指令中直接给出 | MOV AX, 1200H |
| 寄存器寻址 | 寄存器中 | MOV AX, BX |
| 直接寻址 | 内存中,直接给偏移地址 | MOV AX, [1200H] |
| 寄存器间接寻址 | 内存中,偏移地址在寄存器中 | MOV AX, [BX] |
| 寄存器相对寻址 | 内存中,偏移地址=寄存器+位移量 | MOV AL, [BX+5] |
| 基址变址寻址 | 内存中,偏移地址=基址+变址 | MOV AX, [BX+SI] |
| 基址变址相对寻址 | 内存中,偏移地址=基址+变址+位移量 | MOV AL, [BP+DI+5] |
| 隐含寻址 | 默认寄存器中 | MUL BL、CBW |
三、🛠️ 8086指令系统详解
8086指令系统从功能上可以分为六大类:数据传送、算术运算、逻辑运算和移位、串操作、程序控制、处理器控制。下面逐一详解。
📌 8086指令系统分类
| 类别 | 功能 | 是否影响标志位 |
|---|---|---|
| 数据传送 | 数据在寄存器、内存、I/O端口之间传送 | 除标志位操作指令外,不影响 |
| 算术运算 | 加、减、乘、除运算 | 影响 |
| 逻辑运算和移位 | 按位逻辑运算和移位操作 | 影响(NOT除外) |
| 串操作 | 字符串和数据块的批量处理 | 部分影响 |
| 程序控制 | 分支、循环、子程序调用、中断 | 部分影响 |
| 处理器控制 | 标志位操作和CPU状态控制 | 部分影响 |
3.1 📦 数据传送指令
数据传送指令是最常用的一类指令,负责在寄存器、内存和I/O端口之间传送数据。
(1)通用数据传送指令
① 一般数据传送指令MOV
格式: MOV dest, src
功能: 将源操作数src的内容传送到目标操作数dest,src的内容不变。
⚠️ MOV指令的注意事项(初学者最容易犯的错误):
- 两操作数的字长必须相同(都是8位或都是16位)
- 两操作数不能同时为存储器操作数
- 两操作数不能同时为段寄存器
- 源操作数是立即数时,目标操作数不能是段寄存器
- IP和CS不能作为目标操作数,FLAGS一般也不作为操作数
错误指令示例:
assembly
MOV AL, BX ; ❌ 错误,字长不同(AL是8位,BX是16位)
MOV [1200H], [SI] ; ❌ 错误,两个都是存储器操作数
MOV DS, ES ; ❌ 错误,两个都是段寄存器
MOV DS, 1000H ; ❌ 错误,立即数不能直接传送到段寄存器
MOV CS, AX ; ❌ 错误,CS不能作为目标操作数
正确指令示例:
assembly
MOV AL, BL ; ✅ 8位寄存器传送
MOV AX, [SI] ; ✅ 存储器到寄存器
MOV DS, AX ; ✅ 寄存器到段寄存器
MOV [BX], CX ; ✅ 寄存器到存储器
② 堆栈操作指令PUSH和POP
堆栈是内存中一个特殊的区域,遵循先进后出的原则,以字(16位)为单位进行操作。
压栈指令PUSH
格式:PUSH OPRD(16位寄存器或存储器字单元,不能是立即数)
执行过程:
SP = SP - 2(堆栈向低地址方向生长)- 操作数的高字节 →
(SP+1) - 操作数的低字节 →
(SP)
assembly
MOV AX, 1234H
MOV SP, 1200H
PUSH AX ; 执行后SP=11FEH,(11FEH)=34H,(11FFH)=12H
出栈指令POP
格式:POP OPRD(16位寄存器或存储器字单元,不能是立即数,也不能是CS)
执行过程:
(SP)→ 操作数的低字节(SP+1)→ 操作数的高字节SP = SP + 2
assembly
POP AX ; 执行后AX=1234H,SP=1200H
⚠️ PUSH和POP必须成对使用,否则会导致堆栈不平衡,程序运行出错
③ 交换指令XCHG
格式:XCHG reg, mem/reg
功能:将两个操作数的内容互相交换。两个操作数中必须有一个是寄存器,不能使用段寄存器。
assembly
XCHG AX, BX ; 交换AX和BX的内容
XCHG [2000H], CL ; 交换内存2000H单元和CL的内容
④ 字位扩展指令CBW和CWD
用于将有符号数的符号位扩展到高位,以便进行乘除运算。
| 指令 | 功能 | 说明 |
|---|---|---|
CBW |
将AL的符号位扩展到AH | 最高位为1→AH=FFH;最高位为0→AH=00H |
CWD |
将AX的符号位扩展到DX | 最高位为1→DX=FFFFH;最高位为0→DX=0000H |
assembly
MOV AL, 86H ; AL=10000110B,最高位是1
CBW ; 执行后AX=FF86H
MOV AX, 1234H ; AX最高位是0
CWD ; 执行后DX=0000H,AX=1234H
(2)输入输出指令IN和OUT
专门用于CPU和I/O端口之间的数据传送。x86系统中,I/O端口独立编址,地址范围是0000H~FFFFH。
| 指令 | 格式 | 功能 |
|---|---|---|
IN |
IN acc, PORT |
从端口读取数据到累加器 |
OUT |
OUT PORT, acc |
将累加器内容写入端口 |
端口寻址方式:
- 直接寻址:端口地址是8位(00H~FFH),直接给出
- 间接寻址:端口地址是16位(0000H~FFFFH),必须用DX寄存器存放
assembly
IN AL, 80H ; 从80H端口读入8位数据到AL
IN AX, 80H ; 从80H端口读入16位数据到AX(80H→AL,81H→AH)
MOV DX, 2400H
IN AL, DX ; 从2400H端口读入8位数据到AL
OUT 35H, AL ; 将AL的内容写入35H端口
OUT DX, AX ; 将AX的内容写入DX指向的端口
(3)地址传送指令
① 取偏移地址指令LEA
格式:LEA reg, mem
功能:将存储器操作数的16位偏移地址传送到指定的寄存器。
LEA与MOV的区别:
| 指令 | 传送的是 | 示例 | 结果 |
|---|---|---|---|
MOV |
存储器单元的内容 | MOV SI, [1100H] |
SI=内存中的数据 |
LEA |
存储器单元的偏移地址 | LEA SI, [1100H] |
SI=1100H |
💡 LEA的巧妙用法:可以用来做简单的算术运算,比用ADD指令更高效
assembly
LEA AX, [BX+SI+5] ; 相当于 AX = BX + SI + 5
② LDS和LES指令
| 指令 | 格式 | 功能 |
|---|---|---|
LDS |
LDS reg, mem |
从内存取出4字节:前2字节→寄存器,后2字节→DS |
LES |
LES reg, mem |
从内存取出4字节:前2字节→寄存器,后2字节→ES |
assembly
; 假设内存1200H~1203H的内容是:34H, 12H, 00H, 20H
LDS SI, [1200H] ; SI=1234H,DS=2000H
(4)标志位操作指令
| 指令 | 功能 |
|---|---|
LAHF |
将FLAGS寄存器的低8位传送到AH |
SAHF |
将AH的内容传送到FLAGS的低8位 |
PUSHF |
将FLAGS寄存器的内容压入堆栈 |
POPF |
从堆栈中弹出一个字到FLAGS |
3.2 ➕ 算术运算指令
算术运算指令大多数都会影响标志位。
(1)加法指令
① ADD(不带进位加法)
格式:ADD dest, src | 功能:dest = dest + src
影响所有6个状态标志位(CF、PF、AF、ZF、SF、OF)。
assembly
MOV AL, 78H
ADD AL, 99H ; AL=78H+99H=11H,CF=1,SF=0,AF=1,ZF=0,PF=0,OF=0
② ADC(带进位加法)
格式:ADC dest, src | 功能:dest = dest + src + CF
主要用于多字节数的加法运算。
assembly
; 计算 12345678H + 87654321H
MOV AX, 5678H ; 低16位
MOV BX, 4321H
ADD AX, BX ; 低16位相加,进位在CF
MOV CX, 1234H ; 高16位
MOV DX, 8765H
ADC CX, DX ; 高16位相加,加上低16位的进位
; 结果:CX:AX = 99999999H
③ INC(加1指令)
格式:INC OPRD | 功能:OPRD = OPRD + 1
⚠️ 不影响CF标志位,影响其他5个状态标志位。常用于地址指针和循环计数器。
assembly
INC AX ; AX = AX + 1
INC BYTE PTR [BX] ; 内存BX指向的字节单元加1
(2)减法指令
① SUB(不带借位减法)
格式:SUB dest, src | 功能:dest = dest - src
② SBB(带借位减法)
格式:SBB dest, src | 功能:dest = dest - src - CF
主要用于多字节数的减法运算。
③ DEC(减1指令)
格式:DEC OPRD | 功能:OPRD = OPRD - 1
⚠️ 不影响CF标志位,影响其他5个状态标志位。
④ NEG(求补指令)
格式:NEG OPRD | 功能:OPRD = 0 - OPRD
影响所有6个状态标志位。
assembly
MOV AL, 05H
NEG AL ; AL = FBH(-5的补码)
⑤ CMP(比较指令)
格式:CMP OPRD1, OPRD2
功能:执行 OPRD1 - OPRD2,但结果不送回OPRD1,只影响标志位。用于条件转移指令的判断依据。
判断方法:
| 比较类型 | 条件 | 标志位状态 |
|---|---|---|
| 无符号数 | OPRD1 > OPRD2 | CF=0,ZF=0 |
| 无符号数 | OPRD1 = OPRD2 | ZF=1 |
| 无符号数 | OPRD1 < OPRD2 | CF=1 |
| 有符号数 | OPRD1 > OPRD2 | OF=SF,ZF=0 |
| 有符号数 | OPRD1 = OPRD2 | ZF=1 |
| 有符号数 | OPRD1 < OPRD2 | OF≠SF,ZF=0 |
assembly
CMP AX, BX
JA LARGER ; 无符号数,AX>BX则转移
JG GREATER ; 有符号数,AX>BX则转移
⚠️ 注意区分有符号数和无符号数的比较------用的转移指令不同!JA/JB系列用于无符号数,JG/JL系列用于有符号数
(3)乘法指令
| 指令 | 格式 | 字节乘法 | 字乘法 |
|---|---|---|---|
MUL |
无符号数乘法 | AX = AL × OPRD8 |
DX:AX = AX × OPRD16 |
IMUL |
有符号数乘法 | AX = AL × OPRD8 |
DX:AX = AX × OPRD16 |
影响CF和OF:结果高半部分不为0时CF=OF=1,否则CF=OF=0。其他标志位无定义。
assembly
MOV AL, 05H
MOV BL, 03H
MUL BL ; AX = 000FH,CF=OF=0
MOV AL, 80H ; 无符号数128,有符号数-128
MOV BL, 02H
MUL BL ; AX = 0100H(128×2=256),CF=OF=1
IMUL BL ; AX = FF00H(-128×2=-256),CF=OF=1
(4)除法指令
| 指令 | 格式 | 字节除法 | 字除法 |
|---|---|---|---|
DIV |
无符号数除法 | AL=商, AH=余数(AX÷OPRD8) |
AX=商, DX=余数(DX:AX÷OPRD16) |
IDIV |
有符号数除法 | AL=商, AH=余数(AX÷OPRD8) |
AX=商, DX=余数(DX:AX÷OPRD16) |
⚠️ 如果商超出寄存器范围,会产生除法错误中断(中断类型0)。
assembly
MOV AX, 000FH
MOV BL, 03H
DIV BL ; AL=05H,AH=00H
MOV AX, 0005H
MOV BL, 02H
IDIV BL ; AL=02H,AH=01H(余数符号与被除数相同)
💡 ADD/ADC/SUB/SBB对6个状态标志位的影响原理相同;INC/DEC不影响CF;MUL/DIV为隐含寻址方式
3.3 🔣 逻辑运算和移位指令
(1)逻辑运算指令
① AND(逻辑与)
格式:AND dest, src | 功能:dest = dest & src
应用:清零某些位(与0相与)、保留某些位(与1相与)、清零CF和OF。
assembly
AND AL, 0FH ; 清零AL的高4位,保留低4位
AND AX, AX ; 清零CF和OF,AX内容不变
② OR(逻辑或)
格式:OR dest, src | 功能:dest = dest | src
应用:置位某些位(与1相或)、保留某些位(与0相或)、清零CF和OF。
assembly
OR AL, 80H ; 将AL的最高位置1
OR AX, AX ; 清零CF和OF,AX内容不变
③ NOT(逻辑非)
格式:NOT OPRD | 功能:OPRD = ~OPRD(按位取反)
⚠️ 不影响任何标志位。
assembly
MOV AL, 55H ; 01010101B
NOT AL ; AL = AAH(10101010B)
④ XOR(逻辑异或)
格式:XOR dest, src | 功能:dest = dest ^ src
应用:取反某些位(与1异或)、清零寄存器(与自身异或)、清零CF和OF。
assembly
XOR AX, AX ; AX = 0,比 MOV AX, 0 更高效
XOR AL, 0FH ; 取反AL的低4位
⑤ TEST(测试指令)
格式:TEST OPRD1, OPRD2
功能:执行 OPRD1 & OPRD2,但结果不送回OPRD1,只影响标志位。用于测试操作数的某些位是否为1。
assembly
TEST AL, 02H ; 测试AL的第1位是否为1
JNZ BIT1_SET ; 如果第1位为1则转移
(2)移位指令
移动一位时直接给出1,移动两位及以上时,移位次数必须由CL寄存器指定。
① 非循环移位指令
| 指令 | 功能 | 说明 |
|---|---|---|
SAL/SHL |
算术左移/逻辑左移 | 最高位→CF,最低位补0,左移1位=乘以2 |
SHR |
逻辑右移 | 最低位→CF,最高位补0,适用于无符号数 |
SAR |
算术右移 | 最低位→CF,最高位保持不变(符号位扩展) |
⚠️ SHL和SAL的机器码完全相同,只是名称不同。左移一位相当于乘以2,右移一位相当于除以2
assembly
MOV AL, 86H ; 10000110B
SHL AL, 1 ; AL = 00001100B,CF=1
SHR AL, 1 ; AL = 01000011B,CF=0
SAR AL, 1 ; AL = 11000011B,CF=0
② 循环移位指令
| 指令 | 功能 | CF involvement |
|---|---|---|
ROL |
循环左移 | 最高位同时移入CF和最低位 |
ROR |
循环右移 | 最低位同时移入CF和最高位 |
RCL |
带进位循环左移 | CF参与循环,最高位→CF→最低位 |
RCR |
带进位循环右移 | CF参与循环,最低位→CF→最高位 |
assembly
MOV AL, 86H ; 10000110B
ROL AL, 1 ; AL = 00001101B,CF=1
ROR AL, 1 ; AL = 01000011B,CF=0
CLC ; CF=0
RCL AL, 1 ; AL = 00001100B,CF=1
3.4 📜 串操作指令
串操作指令专门用于处理字符串和数据块,能大大提高程序效率。
(1)串操作的共同特点
- 源串默认在数据段DS:SI ,目标串默认在附加段ES:DI
- 串长度存放在CX寄存器中
- 方向标志DF决定地址指针的增减方向:
- DF=0:SI和DI自动递增(字节+1,字+2)
- DF=1:SI和DI自动递减
- 可以加重复前缀实现自动循环
(2)重复前缀
| 前缀 | 条件 | 用途 |
|---|---|---|
REP |
CX≠0就重复 | 串传送、串存储 |
REPE/REPZ |
CX≠0且ZF=1时重复 | 串比较 |
REPNE/REPNZ |
CX≠0且ZF=0时重复 | 串扫描 |
(3)常用串操作指令
① MOVS(串传送)
| 指令 | 功能 |
|---|---|
MOVSB |
字节传送 |
MOVSW |
字传送 |
将DS:SI指向的源串传送到ES:DI指向的目标串,通常与REP连用。
assembly
; 将数据段MEM1开始的200个字节传送到附加段MEM2开始的区域
LEA SI, MEM1 ; 源串地址
LEA DI, MEM2 ; 目标串地址
MOV CX, 200 ; 串长度
CLD ; DF=0,地址递增
REP MOVSB ; 重复传送200个字节
HLT
② CMPS(串比较)
| 指令 | 功能 |
|---|---|
CMPSB |
字节比较 |
CMPSW |
字比较 |
将DS:SI指向的源串与ES:DI指向的目标串比较,通常与REPE连用。
assembly
; 比较两个长度为200的字符串是否相等
LEA SI, STR1
LEA DI, STR2
MOV CX, 200
CLD
REPE CMPSB ; 比较直到不相等或CX=0
JZ EQUAL ; 如果ZF=1,说明两个串相等
; 不相等的处理
EQUAL:
; 相等的处理
③ SCAS(串扫描)
| 指令 | 功能 |
|---|---|
SCASB |
字节扫描 |
SCASW |
字扫描 |
将AL/AX的内容与ES:DI指向的目标串比较,通常与REPNE连用查找关键字。
assembly
; 在字符串STR中查找字符'A'
LEA DI, STR
MOV AL, 'A'
MOV CX, 100 ; 字符串长度
CLD
REPNE SCASB ; 查找直到找到'A'或CX=0
JZ FOUND ; 如果ZF=1,说明找到
; 没找到的处理
FOUND:
; 找到的处理,DI-1是'A'的地址
④ LODS(串装入)和 STOS(串存储)
| 指令 | 功能 |
|---|---|
LODSB |
将DS:SI指向的字节装入AL |
LODSW |
将DS:SI指向的字装入AX |
STOSB |
将AL存入ES:DI指向的字节 |
STOSW |
将AX存入ES:DI指向的字 |
assembly
; 将1000H开始的100个字节初始化为2AH
LEA DI, 1000H
MOV AL, 2AH
MOV CX, 100
CLD
REP STOSB
HLT
💡 串操作指令是8086中唯一能一次处理多个数据的指令,合理使用重复前缀可以大大简化程序
3.5 🔀 程序控制指令
程序控制指令用于改变程序的执行流程,实现分支、循环、过程调用和中断。
(1)转移指令
① 无条件转移指令JMP
格式:JMP OPRD
| 类型 | 说明 | 示例 |
|---|---|---|
| 段内直接转移 | 目标地址在当前代码段内 | JMP LABEL |
| 段内间接转移 | 偏移地址在寄存器或存储器中 | JMP BX、JMP WORD PTR [BX] |
| 段间直接转移 | 目标地址在另一个代码段 | JMP FAR LABEL |
| 段间间接转移 | 32位地址在存储器双字单元中 | JMP DWORD PTR [BX] |
② 条件转移指令
所有条件转移指令都是段内短转移 ,转移范围是 -128~+127 字节。
常用条件转移指令一览:
| 指令 | 转移条件 | 用途 |
|---|---|---|
JC |
CF=1 | 有进位/借位转移 |
JNC |
CF=0 | 无进位/借位转移 |
JZ/JE |
ZF=1 | 等于/为零转移 |
JNZ/JNE |
ZF=0 | 不等于/不为零转移 |
JO |
OF=1 | 溢出转移 |
JNO |
OF=0 | 无溢出转移 |
JP/JPE |
PF=1 | 奇偶位为1转移 |
JNP/JPO |
PF=0 | 奇偶位为0转移 |
JA/JNBE |
CF=0且ZF=0 | 无符号数大于 |
JAE/JNB |
CF=0或ZF=1 | 无符号数大于等于 |
JB/JNAE |
CF=1且ZF=0 | 无符号数小于 |
JBE/JNA |
CF=1或ZF=1 | 无符号数小于等于 |
JG/JNLE |
OF=SF且ZF=0 | 有符号数大于 |
JGE/JNL |
OF=SF或ZF=1 | 有符号数大于等于 |
JL/JNGE |
OF≠SF且ZF=0 | 有符号数小于 |
JLE/JNG |
OF≠SF或ZF=1 | 有符号数小于等于 |
示例:统计100个8位带符号数中正数的个数
assembly
LEA SI, BUF
MOV CX, 100
XOR BX, BX ; BX用于计数,初始化为0
CLD
NEXT:
LODSB ; 取一个字节到AL
TEST AL, AL ; 测试AL的符号位
JS NEGATIVE ; 如果是负数,跳过
INC BX ; 正数,计数加1
NEGATIVE:
DEC CX
JNZ NEXT
; 结果在BX中
(2)循环控制指令
循环控制指令使用CX作为循环计数器,转移范围也是 -128~+127 字节。
| 指令 | 循环条件 | 说明 |
|---|---|---|
LOOP |
CX≠0 | CX-1,若CX≠0则跳转 |
LOOPZ/LOOPE |
CX≠0且ZF=1 | CX-1,若CX≠0且ZF=1则跳转 |
LOOPNZ/LOOPNE |
CX≠0且ZF=0 | CX-1,若CX≠0且ZF=0则跳转 |
示例:计算1到100的和
assembly
XOR AX, AX ; AX用于存放和,初始化为0
MOV CX, 100 ; 循环次数
SUM:
ADD AX, CX
LOOP SUM
; 结果AX = 5050
(3)过程调用和返回指令
① CALL(过程调用)
CALL指令先将断点地址压入堆栈,然后跳转到子过程入口。
| 类型 | 说明 |
|---|---|
| 段内直接调用 | 只压入IP |
| 段内间接调用 | 只压入IP |
| 段间直接调用 | 压入CS和IP |
| 段间间接调用 | 压入CS和IP |
assembly
CALL NEAR PROC ; 段内直接调用
CALL FAR PROC ; 段间直接调用
CALL WORD PTR [BX] ; 段内间接调用
CALL DWORD PTR [BX] ; 段间间接调用
② RET(过程返回)
从堆栈中弹出断点地址,返回调用程序。子程序的最后一条指令必须是RET。
示例:一个简单的加法过程
assembly
ADD_PROC PROC NEAR
ADD AX, BX
RET
ADD_PROC ENDP
; 调用过程
MOV AX, 1234H
MOV BX, 5678H
CALL ADD_PROC ; 调用后AX = 68ACH
⚠️ 过程中用到的寄存器要用PUSH保存、POP恢复,确保子过程不会破坏调用者的现场
(4)中断指令
① INT n(中断指令)
格式:INT n(n是中断类型码,0~255)
执行过程:
- 将FLAGS寄存器压入堆栈
- 清除IF和TF标志位
- 将CS和IP压入堆栈
- 从中断向量表取出入口地址(n×4处的双字),分别送入IP和CS
- 跳转到中断服务程序执行
assembly
MOV AH, 02H ; 功能号:显示字符
MOV DL, 'A' ; 要显示的字符
INT 21H ; 调用DOS中断
② IRET(中断返回)
从堆栈中弹出IP、CS和FLAGS,返回原程序。中断服务程序的最后一条指令必须是IRET。
3.6 ⚙️ 处理器控制指令
(1)标志位操作指令
| 指令 | 功能 |
|---|---|
CLC |
CF = 0 |
STC |
CF = 1 |
CMC |
CF = ~CF |
CLD |
DF = 0(串操作地址递增) |
STD |
DF = 1(串操作地址递减) |
CLI |
IF = 0(关中断) |
STI |
IF = 1(开中断) |
(2)其他处理器控制指令
| 指令 | 功能 |
|---|---|
HLT |
暂停,直到有中断或复位信号 |
NOP |
空操作,占1字节代码空间 |
WAIT |
等待外部协处理器完成操作 |
ESC |
交权指令,控制权交给协处理器 |
LOCK |
总线锁定前缀,保证原子性 |
四、🤔 几个思考题
学完本文,来试试回答这些问题:
1️⃣ MOV指令有哪些限制条件?
答: MOV指令有五大限制:(1) 两操作数字长必须相同;(2) 两操作数不能同时为存储器操作数;(3) 两操作数不能同时为段寄存器;(4) 源操作数是立即数时目标不能是段寄存器;(5) IP和CS不能作为目标操作数。
💡 这些限制是由8086硬件设计决定的,如果违反了规则,汇编器会报错。
2️⃣ LEA和MOV有什么区别?
答: MOV传送的是存储器单元的内容 ,而LEA传送的是存储器单元的偏移地址 。例如MOV SI, [1100H]让SI等于内存1100H处存放的数据,而LEA SI, [1100H]让SI等于1100H这个地址值本身。LEA还可以做简单算术运算,如LEA AX, [BX+SI+5]等价于AX = BX + SI + 5。
3️⃣ 比较两个有符号数和无符号数的大小,分别用什么条件转移指令?
答: 无符号数比较用JA/JAE/JB/JBE系列指令(基于CF和ZF判断),有符号数比较用JG/JGE/JL/JLE系列指令(基于OF、SF和ZF判断)。混用会导致错误的判断结果。
4️⃣ PUSH和POP操作数为什么必须是16位的?
答: 因为8086的堆栈以字(16位)为单位进行操作,SP每次变化2。如果允许8位操作,会导致SP的变化量不一致,破坏堆栈的对称性,造成严重错误。
5️⃣ 串操作指令中DF标志位的作用是什么?
答: DF(方向标志)决定串操作后SI和DI的修改方向。DF=0时地址递增(正向处理),DF=1时地址递减(反向处理)。通过CLD/STD指令可以设置DF的值。
📚 学习总结与建议
重点掌握内容
- 寻址方式:特别是5种存储器寻址方式,这是汇编的难点也是重点
- 数据传送指令:MOV、PUSH、POP、LEA、IN、OUT是最常用的指令
- 算术运算指令:ADD、SUB、CMP、MUL、DIV,注意对标志位的影响
- 逻辑运算和移位指令:AND、OR、XOR、TEST、SAL、SHR,底层编程必备
- 串操作指令:MOVS、CMPS、SCAS、STOS,能极大提高数据处理效率
- 程序控制指令:JMP、条件转移、LOOP、CALL、RET,流程控制的关键
学习建议
- 多写代码:从简单的数据块传送、排序、查找开始练习
- 多调试:使用DOSBox中的DEBUG工具单步执行,观察寄存器和内存变化
- 理解标志位:标志位是汇编语言的灵魂,很多指令的结果都体现在标志位上
- 循序渐进:先掌握16位汇编,再学习32位和64位,不要急于求成
✅ 本节完
📝 作者:say-fall | 编辑:say-fall | 🌟 原创不易,如果对你有帮助,记得 👍 点赞 + ⭐ 收藏 哦!