从零开始学x86汇编_16位指令系统完全指南


🌈 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处理器寻址方式)
    • [三、🛠️ 8086指令系统详解](#三、🛠️ 8086指令系统详解)
      • [📌 8086指令系统分类](#📌 8086指令系统分类)
      • [3.1 📦 数据传送指令](#3.1 📦 数据传送指令)
      • [3.2 ➕ 算术运算指令](#3.2 ➕ 算术运算指令)
        • (1)加法指令
          • [① ADD(不带进位加法)](#① ADD(不带进位加法))
          • [② ADC(带进位加法)](#② ADC(带进位加法))
          • [③ INC(加1指令)](#③ INC(加1指令))
        • (2)减法指令
          • [① SUB(不带借位减法)](#① SUB(不带借位减法))
          • [② SBB(带借位减法)](#② SBB(带借位减法))
          • [③ DEC(减1指令)](#③ DEC(减1指令))
          • [④ NEG(求补指令)](#④ NEG(求补指令))
          • [⑤ CMP(比较指令)](#⑤ CMP(比较指令))
        • (3)乘法指令
        • (4)除法指令
      • [3.3 🔣 逻辑运算和移位指令](#3.3 🔣 逻辑运算和移位指令)
        • (1)逻辑运算指令
          • [① AND(逻辑与)](#① AND(逻辑与))
          • [② OR(逻辑或)](#② OR(逻辑或))
          • [③ NOT(逻辑非)](#③ NOT(逻辑非))
          • [④ XOR(逻辑异或)](#④ XOR(逻辑异或))
          • [⑤ TEST(测试指令)](#⑤ TEST(测试指令))
        • (2)移位指令
          • [① 非循环移位指令](#① 非循环移位指令)
          • [② 循环移位指令](#② 循环移位指令)
      • [3.4 📜 串操作指令](#3.4 📜 串操作指令)
      • [3.5 🔀 程序控制指令](#3.5 🔀 程序控制指令)
      • [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, BXADD 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 BLCBW

三、🛠️ 8086指令系统详解

8086指令系统从功能上可以分为六大类:数据传送、算术运算、逻辑运算和移位、串操作、程序控制、处理器控制。下面逐一详解。

📌 8086指令系统分类

类别 功能 是否影响标志位
数据传送 数据在寄存器、内存、I/O端口之间传送 除标志位操作指令外,不影响
算术运算 加、减、乘、除运算 影响
逻辑运算和移位 按位逻辑运算和移位操作 影响(NOT除外)
串操作 字符串和数据块的批量处理 部分影响
程序控制 分支、循环、子程序调用、中断 部分影响
处理器控制 标志位操作和CPU状态控制 部分影响

3.1 📦 数据传送指令

数据传送指令是最常用的一类指令,负责在寄存器、内存和I/O端口之间传送数据。

(1)通用数据传送指令
① 一般数据传送指令MOV

格式: MOV dest, src

功能: 将源操作数src的内容传送到目标操作数dest,src的内容不变。

⚠️ MOV指令的注意事项(初学者最容易犯的错误):

  1. 两操作数的字长必须相同(都是8位或都是16位)
  2. 两操作数不能同时为存储器操作数
  3. 两操作数不能同时为段寄存器
  4. 源操作数是立即数时,目标操作数不能是段寄存器
  5. 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位寄存器或存储器字单元,不能是立即数)

执行过程:

  1. SP = SP - 2(堆栈向低地址方向生长)
  2. 操作数的高字节 → (SP+1)
  3. 操作数的低字节 → (SP)
assembly 复制代码
MOV AX, 1234H
MOV SP, 1200H
PUSH AX    ; 执行后SP=11FEH,(11FEH)=34H,(11FFH)=12H

出栈指令POP

格式:POP OPRD(16位寄存器或存储器字单元,不能是立即数,也不能是CS)

执行过程:

  1. (SP) → 操作数的低字节
  2. (SP+1) → 操作数的高字节
  3. 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 BXJMP 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)

执行过程:

  1. 将FLAGS寄存器压入堆栈
  2. 清除IF和TF标志位
  3. 将CS和IP压入堆栈
  4. 从中断向量表取出入口地址(n×4处的双字),分别送入IP和CS
  5. 跳转到中断服务程序执行
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的值。

📚 学习总结与建议

重点掌握内容

  1. 寻址方式:特别是5种存储器寻址方式,这是汇编的难点也是重点
  2. 数据传送指令:MOV、PUSH、POP、LEA、IN、OUT是最常用的指令
  3. 算术运算指令:ADD、SUB、CMP、MUL、DIV,注意对标志位的影响
  4. 逻辑运算和移位指令:AND、OR、XOR、TEST、SAL、SHR,底层编程必备
  5. 串操作指令:MOVS、CMPS、SCAS、STOS,能极大提高数据处理效率
  6. 程序控制指令:JMP、条件转移、LOOP、CALL、RET,流程控制的关键

学习建议

  • 多写代码:从简单的数据块传送、排序、查找开始练习
  • 多调试:使用DOSBox中的DEBUG工具单步执行,观察寄存器和内存变化
  • 理解标志位:标志位是汇编语言的灵魂,很多指令的结果都体现在标志位上
  • 循序渐进:先掌握16位汇编,再学习32位和64位,不要急于求成

✅ 本节完

📝 作者:say-fall | 编辑:say-fall | 🌟 原创不易,如果对你有帮助,记得 👍 点赞 + ⭐ 收藏 哦!

相关推荐
XMYX-018 小时前
39 - Go 信号捕获与处理:优雅退出、进程控制
开发语言·golang
是星辰吖~18 小时前
C++_vector_调用及模拟实现
开发语言·c++
薛定e的猫咪18 小时前
从 DSM 到多智能体仿真:复杂产品变更传播研究路线图
开发语言·人工智能·笔记·学习·php
粉嘟小飞妹儿18 小时前
Java Switch与Break用法详解
java·开发语言
艾莉丝努力练剑18 小时前
【QT】常用控件(三)Qt布局管理器(网格/表单/间隔器)
java·linux·运维·服务器·开发语言·网络·qt
川冰ICE18 小时前
JavaScript入门⑩|BOM与浏览器对象,localStorage_位置_历史记录
开发语言·javascript·ecmascript
折哥的程序人生 · 物流技术专研18 小时前
Java 23 种设计模式:从踩坑到精通 —— 开篇及系列介绍
java·开发语言·后端·设计模式·面试·架构
ch.ju18 小时前
Java程序设计(第3版)第四章——构造方法
java·开发语言
阿里嘎多学长18 小时前
2026-05-25 GitHub 热点项目精选
开发语言·程序员·github·代码托管