8086汇编程序设计_从基础到实战


🌈 say-fall:个人主页 🚀 专栏:《手把手教你学会C++》 | 《系统深入Linux操作系统》 | 《数据结构与算法》 | 《小游戏与项目》 💪 格言:做好你自己,才能吸引更多的人,与他们共赢,这才是最好的成长方式。

📝 前言

学了8086的一大堆指令------MOV、ADD、CMP、JMP......你可能会想:指令我都背得差不多了,可是怎么才能把它们组织成一个真正能跑起来的程序 呢?确实,知道每条指令是什么意思和写出完整的汇编程序之间,还隔着一道重要的门槛:程序结构设计

在高级语言中,我们随手写个print("Hello")就能输出,但在汇编里,你得自己定义数据段、初始化段寄存器、通过中断调用来实现输入输出......每一个细节都由你掌控,这既是汇编的难点,也是它最迷人的地方------你能精确地控制计算机的每一个行为

本文将从零开始,带你系统地学习汇编程序设计的完整知识体系,包括伪指令、系统功能调用、基本程序结构,最后通过一个完整的实例把所有知识串联起来。

通过本文,你将掌握:

技能 应用场景
汇编源程序的完整结构 编写可编译运行的.asm程序
常用伪指令(DB/DW/EQU/SEGMENT等) 定义数据、段、常量
DOS/BIOS系统功能调用 实现键盘输入、屏幕输出
四种基本程序结构 顺序、循环、分支、子程序
宏定义与子程序 代码复用和模块化编程

📌 前置知识: 掌握8086的六大类指令(数据传送、算术运算、逻辑运算和移位、串操作、程序控制),了解段寄存器和内存寻址的基本概念。

文章目录

    • [📝 前言](#📝 前言)
    • [一、🔧 汇编语言程序设计基础](#一、🔧 汇编语言程序设计基础)
    • [二、📐 伪指令详解](#二、📐 伪指令详解)
      • [1️⃣ 数据定义伪指令](#1️⃣ 数据定义伪指令)
      • [2️⃣ 符号定义伪指令EQU](#2️⃣ 符号定义伪指令EQU)
      • [3️⃣ 段定义伪指令](#3️⃣ 段定义伪指令)
      • [4️⃣ 完整的汇编源程序结构](#4️⃣ 完整的汇编源程序结构)
      • [5️⃣ 结束伪指令END](#5️⃣ 结束伪指令END)
      • [6️⃣ 过程定义伪指令](#6️⃣ 过程定义伪指令)
      • [7️⃣ 宏命令伪指令](#7️⃣ 宏命令伪指令)
      • [8️⃣ ORG伪指令](#8️⃣ ORG伪指令)
    • [三、💻 系统功能调用](#三、💻 系统功能调用)
    • [四、🏗️ 汇编语言程序设计基本方法](#四、🏗️ 汇编语言程序设计基本方法)
    • [五、🎯 完整程序示例:输入字符串并显示](#五、🎯 完整程序示例:输入字符串并显示)
    • [六、🤔 几个思考题](#六、🤔 几个思考题)
      • [1️⃣ ASSUME伪指令的作用是什么?它是否实际给段寄存器赋值?](#1️⃣ ASSUME伪指令的作用是什么?它是否实际给段寄存器赋值?)
      • [2️⃣ 小端模式下,`DW 3344H`在内存中如何存储?](#2️⃣ 小端模式下,DW 3344H在内存中如何存储?)
      • [3️⃣ DOS功能调用09H显示字符串时,对字符串有什么特殊要求?](#3️⃣ DOS功能调用09H显示字符串时,对字符串有什么特殊要求?)
      • [4️⃣ 宏和子程序各适用于什么场景?](#4️⃣ 宏和子程序各适用于什么场景?)
      • [5️⃣ 为什么字符串输入缓冲区的第0字节是最大字符数,第1字节是实际字符数?](#5️⃣ 为什么字符串输入缓冲区的第0字节是最大字符数,第1字节是实际字符数?)
    • [📚 学习总结与建议](#📚 学习总结与建议)

一、🔧 汇编语言程序设计基础

1️⃣ 汇编源程序与汇编程序

很多初学者容易混淆这两个概念,它们有着本质的区别:

概念 说明 扩展名
汇编语言源程序 程序员编写的文本代码 .asm
汇编程序 将源程序翻译成机器语言的软件(如MASM、TASM) ---
目标程序 汇编生成的机器语言文件 .obj
链接程序 将目标程序和库文件链接成可执行程序的软件(如LINK) ---
可执行程序 最终可在操作系统上运行的文件 .exe / .com
汇编语言程序开发的完整流程
复制代码
编辑(.asm) → 汇编(MASM) → 目标(.obj) → 链接(LINK) → 可执行(.exe) → 调试/运行

具体命令:

bash 复制代码
MASM myprogram.asm;    ; 汇编,生成 .obj 文件
LINK myprogram.obj;    ; 链接,生成 .exe 文件

💡 实际开发中推荐使用DOSBox + MASM/TASM环境,配合TD或DEBUG进行调试

2️⃣ 汇编语言语句类型

汇编语言源程序由两种基本类型的语句组成:

语句类型 执行主体 功能 是否生成目标代码
指令性语句 CPU 执行具体操作(如MOV、ADD)
指示性语句(伪指令) 汇编程序 告诉汇编程序如何处理源程序

3️⃣ 汇编语言语句格式

(1)指令性语句格式
asm 复制代码
[标号:] [前缀] 助记符 [操作数], [操作数] [;注释]
  • 标号 :指令的符号地址,后面跟冒号:,用于程序转移
  • 前缀:指令前缀,如REP、LOCK等
  • 助记符:操作码,如MOV、ADD等
  • 操作数:寄存器、存储器、常量等
  • 注释 :以分号;开头,不影响程序执行
(2)指示性语句(伪指令)格式
asm 复制代码
[名字] 伪指令助记符 操作数 [, 操作数, ...] [;注释]

⚠️ 注意区别:指令性语句的标号后面有冒号,伪指令的名字后面没有冒号

4️⃣ 操作数的类型

类型 示例 说明
寄存器 AX、BX、AL CPU内部寄存器
存储器单元 [1200H]、[BX]、DATA1 内存中的数据
常量 1234H、0FFH、'A' 数字常量或字符常量
变量 DATA1、BUF 内存数据区的符号地址,具有段值、偏移地址和类型三个属性
标号 START、NEXT 指令的符号地址,用于程序转移
表达式 OFFSET DATA1、30H+99H 由常量、变量和运算符组成,汇编时计算

5️⃣ 常用运算符

(1)取值运算符
运算符 功能 示例
OFFSET 获取偏移地址 MOV BX, OFFSET DATA1
SEG 获取段地址 MOV AX, SEG DATA1
(2)属性运算符PTR

用于临时改变存储器操作数的类型。

格式:类型 PTR 存储器操作数

asm 复制代码
MOV BYTE PTR [BX], 12H    ; 将12H作为字节写入
MOV WORD PTR [BX], 12H    ; 将12H作为字写入

二、📐 伪指令详解

伪指令不被CPU执行,但决定了源程序的结构数据的组织方式

1️⃣ 数据定义伪指令

伪指令 变量类型 占用字节数 说明
DB 字节型 1 定义字节变量,也用于定义字符串
DW 字型 2 定义字变量(小端模式存储)
DD 双字型 4 定义双字变量
DQ 四字型 8 定义四字变量
DT 十字节型 10 定义十字节变量

定义字符串必须使用DB伪指令,因为每个字符占一个字节。

asm 复制代码
DSEG SEGMENT
    DATA1 DB 11H, 22H, 33H, 44H         ; 定义4个字节
    DATA2 DW 11H, 22H, 3344H             ; 定义3个字
    DATA3 DD 11H*2, 22H, 33445566H       ; 定义3个双字
    DATA4 DB 'ABCD', 66H                  ; 字符串+一个字节
DSEG ENDS

这些变量在内存中的存储方式(小端模式:低字节在低地址):

变量 内存内容 说明
DATA1 11H 22H 33H 44H 字节按顺序存放
DATA2 11H 00H 22H 00H 44H 33H 字的低字节在前
DATA4 41H 42H 43H 44H 66H 'A'=41H,'B'=42H......
DUP重复操作符和?
asm 复制代码
M1 DB 10 DUP(?)             ; 预留10个字节空间,内容随机
M2 DB 34H, 'A', ?           ; 定义3个字节,最后一个随机
M3 DW 3 DUP(11H, 22H)       ; 3组字,每组11H和22H,共6个字节

⚠️ 小端模式是x86架构的核心特征,低字节存在低地址、高字节存在高地址,务必牢记

2️⃣ 符号定义伪指令EQU

格式:符号名 EQU 表达式

用符号名取代表达式,使程序更易读和维护。EQU定义的符号不能重新定义,不占用内存空间。

asm 复制代码
CONSTANT EQU 100               ; 定义常量
VAR EQU 30H+99H                ; 定义表达式(汇编时计算为C9H)
MESSAGE EQU 'Hello, World!'    ; 定义字符串常量

3️⃣ 段定义伪指令

8086汇编语言采用分段结构,程序由代码段、数据段、附加段和堆栈段组成。

基本格式
asm 复制代码
段名 SEGMENT [定位类型] [组合类型] ['类别']
    ; 段内内容
段名 ENDS
设定段寄存器伪指令ASSUME

格式:ASSUME 段寄存器名:段名[, 段寄存器名:段名, ...]

⚠️ ASSUME只是建立对应关系,并没有实际给段寄存器赋值!除了CS由系统自动初始化外,DS、ES、SS都需要在程序中手动赋值,且不能直接用立即数,必须通过AX中转

4️⃣ 完整的汇编源程序结构

asm 复制代码
; ===== 数据段:存放程序中的数据 =====
数据段名 SEGMENT
    ; 变量定义
数据段名 ENDS

; ===== 附加段:用于串操作等 =====
附加段名 SEGMENT
    ; 附加数据定义
附加段名 ENDS

; ===== 堆栈段:存放堆栈数据 =====
堆栈段名 SEGMENT STACK 'STACK'
    ; 堆栈空间定义
堆栈段名 ENDS

; ===== 代码段:存放程序指令 =====
代码段名 SEGMENT
    ASSUME CS:代码段名, DS:数据段名, ES:附加段名, SS:堆栈段名
START:
    ; 初始化段寄存器(必须!)
    MOV AX, 数据段名
    MOV DS, AX
    MOV AX, 附加段名
    MOV ES, AX
    MOV AX, 堆栈段名
    MOV SS, AX

    ; ===== 程序主体代码 =====

    ; 返回DOS
    MOV AH, 4CH
    INT 21H
代码段名 ENDS
END START

关键说明:

要点 说明
代码段 每个程序必须定义
数据段 有内存操作时需要定义
附加段 有串操作时必须定义
堆栈段 有堆栈操作或子程序调用时需要定义
段寄存器赋值 不能直接用立即数,必须通过AX中转

5️⃣ 结束伪指令END

格式:END [标号]

表示源程序结束,标号指定程序的入口地址

6️⃣ 过程定义伪指令

asm 复制代码
过程名 PROC [NEAR/FAR]
    ; 过程体
    RET          ; 必须是最后一条指令!
过程名 ENDP
  • NEAR:近过程,与调用程序在同一代码段
  • FAR:远过程,与调用程序在不同代码段

示例:延时子程序

asm 复制代码
; 大约延时BL*10ms
DELAY PROC
    PUSH BX
    PUSH CX
    MOV BL, 2
NEXT:
    MOV CX, 4167         ; 循环4167次约10ms
W10M:
    LOOP W10M
    DEC BL
    JNZ NEXT
    POP CX
    POP BX
    RET
DELAY ENDP

; 调用方式
CALL DELAY

7️⃣ 宏命令伪指令

宏与子程序的区别
特性 子程序
处理时机 汇编时展开 运行时调用
代码大小 每次调用都展开,代码量大 只有一份,代码量小
执行速度 快,无调用返回开销 慢,有调用返回开销
参数传递 灵活,直接替换 通过寄存器或堆栈
宏的定义和调用
asm 复制代码
; 宏定义:将X和Y相加,结果存入Z
DADD MACRO X, Y, Z
    MOV AX, X
    ADD AX, Y
    MOV Z, AX
ENDM

; 宏调用
DADD DATA1, DATA2, SUM

; 汇编时自动展开为:
; MOV AX, DATA1
; ADD AX, DATA2
; MOV SUM, AX

8️⃣ ORG伪指令

格式:ORG 偏移地址

设置段内程序或变量的起始偏移地址。

asm 复制代码
DATA SEGMENT
    ORG 1000H        ; 下面变量从1000H开始
    M1 DB 1, 2, 3
    ORG 2000H        ; 下面变量从2000H开始
    M2 DB 3 DUP(?)
DATA ENDS

三、💻 系统功能调用

系统功能调用是操作系统提供给程序员的一组子程序,用于完成输入输出、文件管理等常用功能。

1️⃣ DOS/BIOS功能调用概述

层级 说明 特点
BIOS(基本输入输出系统) 驻留在ROM中,提供最底层硬件控制 更接近硬件,功能更基础
DOS(磁盘操作系统) 在BIOS之上,提供更高级功能 更易使用,功能更丰富

调用的基本步骤:

  1. 将调用参数装入指定寄存器
  2. 将功能号送入AH寄存器
  3. 执行中断指令(如INT 21H
  4. 检查返回参数

2️⃣ 常用DOS功能调用(INT 21H)

(1)单字符输入(功能号01H)
项目 说明
功能 从键盘输入一个字符,回显到屏幕
入口参数
出口参数 AL = 输入字符的ASCII码
asm 复制代码
; 等待用户输入Y或N
GET_KEY:
    MOV AH, 01H
    INT 21H
    CMP AL, 'Y'
    JZ YES
    CMP AL, 'N'
    JZ NO
    JMP GET_KEY
YES:
    ; 用户输入Y的处理
NO:
    ; 用户输入N的处理
(2)字符串输入(功能号0AH)
项目 说明
功能 从键盘输入字符串,存入指定缓冲区
入口参数 DS:DX = 输入缓冲区的首地址
出口参数 字符串存放在缓冲区中

输入缓冲区的格式(重点!):

复制代码
偏移量    内容                说明
+0       最大字符数           用户设置(包括回车符)
+1       实际字符数           DOS自动填写(不包括回车)
+2起     字符串内容           最后以回车符0DH结束
asm 复制代码
DATA SEGMENT
    BUFF DB 100, ?, 100 DUP(?)    ; 最大100个字符
DATA ENDS

CODE SEGMENT
    ASSUME CS:CODE, DS:DATA
START:
    MOV AX, DATA
    MOV DS, AX
    LEA DX, BUFF
    MOV AH, 0AH
    INT 21H              ; 输入字符串
    ; BUFF+1是实际输入的字符数,BUFF+2是字符串起始地址
    MOV AH, 4CH
    INT 21H
CODE ENDS
END START
(3)单字符显示输出(功能号02H)
项目 说明
功能 在屏幕上显示一个字符
入口参数 DL = 要显示字符的ASCII码
asm 复制代码
MOV AH, 02H
MOV DL, 'A'
INT 21H               ; 屏幕显示字符'A'
(4)字符串显示输出(功能号09H)
项目 说明
功能 在屏幕上显示字符串
入口参数 DS:DX = 字符串首地址

⚠️ 字符串必须以'$'(ASCII码24H)结束,否则会一直显示到内存中出现$为止

asm 复制代码
DATA SEGMENT
    MESS DB 'Input String:', 0DH, 0AH, '$'   ; 0DH回车,0AH换行
DATA ENDS

CODE SEGMENT
    ASSUME CS:CODE, DS:DATA
START:
    MOV AX, DATA
    MOV DS, AX
    LEA DX, MESS
    MOV AH, 09H
    INT 21H               ; 显示字符串
    MOV AH, 4CH
    INT 21H
CODE ENDS
END START
(5)返回操作系统(功能号4CH)
asm 复制代码
MOV AH, 4CH
INT 21H               ; 程序结束,返回DOS

3️⃣ 常用BIOS功能调用

键盘状态检验(INT 16H,功能号01H)
项目 说明
功能 检查是否有键按下,不等待
出口参数 ZF=0有键按下(AX=扫描码+ASCII码);ZF=1无键按下

示例:循环显示信息,按任意键退出

asm 复制代码
DSEG SEGMENT
    MESS DB 'Hello, World!', 0DH, 0AH, '$'
DSEG ENDS

CSEG SEGMENT
    ASSUME CS:CSEG, DS:DSEG
START:
    MOV AX, DSEG
    MOV DS, AX
AGAIN:
    LEA DX, MESS
    MOV AH, 09H
    INT 21H              ; 显示信息
    MOV AH, 01H
    INT 16H              ; 检查是否有键按下
    JZ AGAIN             ; 无键按下,继续循环
    MOV AH, 4CH
    INT 21H
CSEG ENDS
END START

💡 INT 16H检查键盘不会阻塞程序执行,适合需要"非阻塞"键盘检测的场景。而INT 21H功能01H会等待用户按键,属于"阻塞"方式

四、🏗️ 汇编语言程序设计基本方法

1️⃣ 程序设计的基本步骤

复制代码
分析问题 → 确定算法 → 画流程图 → 分配资源 → 编写代码 → 调试程序

2️⃣ 四种基本程序结构

(1)顺序结构

最简单的结构,程序按指令顺序依次执行。

asm 复制代码
; 计算 1 + 2 + 3
MOV AL, 1
ADD AL, 2
ADD AL, 3          ; 结果AL = 6
(2)循环结构

由三部分组成:初始化 → 循环体 → 循环控制

asm 复制代码
; 计算1到100的和
XOR AX, AX         ; 和初始化为0
MOV CX, 100        ; 循环次数
SUM:
    ADD AX, CX
    LOOP SUM        ; CX-1,不为0则继续
; 结果AX = 5050
(3)分支结构

根据条件执行不同的代码路径。

asm 复制代码
; 比较两个数,将较大值存入MAX
MOV AX, NUM1
MOV BX, NUM2
CMP AX, BX
JAE LARGER        ; AX >= BX则跳转
MOV MAX, BX
JMP EXIT
LARGER:
MOV MAX, AX
EXIT:
(4)子程序结构

将独立功能封装为子程序,实现代码复用。前面已详细介绍过程定义和调用方法。

五、🎯 完整程序示例:输入字符串并显示

下面是一个完整的可运行程序,实现从键盘输入字符串、然后在屏幕上显示出来:

asm 复制代码
; ========================================
; 功能:输入字符串并显示
; ========================================
DATA SEGMENT
    INPUT_MSG  DB 'Please input a string: $'
    OUTPUT_MSG DB 'Your input is: $'
    BUFF       DB 100, ?, 100 DUP(?)    ; 输入缓冲区
    CRLF       DB 0DH, 0AH, '$'          ; 回车换行
DATA ENDS

CODE SEGMENT
    ASSUME CS:CODE, DS:DATA
START:
    ; ---- 初始化数据段 ----
    MOV AX, DATA
    MOV DS, AX

    ; ---- 显示输入提示 ----
    LEA DX, INPUT_MSG
    MOV AH, 09H
    INT 21H

    ; ---- 输入字符串 ----
    LEA DX, BUFF
    MOV AH, 0AH
    INT 21H

    ; ---- 回车换行 ----
    LEA DX, CRLF
    MOV AH, 09H
    INT 21H

    ; ---- 显示输出提示 ----
    LEA DX, OUTPUT_MSG
    MOV AH, 09H
    INT 21H

    ; ---- 显示输入的字符串 ----
    LEA DX, BUFF+2         ; 字符串从BUFF+2开始
    MOV BL, BUFF+1         ; 实际输入的字符数
    MOV BH, 0
    MOV BYTE PTR [BX+DX], '$'   ; 在末尾添加$结束符
    MOV AH, 09H
    INT 21H

    ; ---- 回车换行 ----
    LEA DX, CRLF
    MOV AH, 09H
    INT 21H

    ; ---- 返回DOS ----
    MOV AH, 4CH
    INT 21H
CODE ENDS
END START

💡 这个程序综合运用了数据段定义、段寄存器初始化、字符串输入(0AH)、字符串输出(09H)等核心知识点,是学习汇编程序设计的经典入门程序

六、🤔 几个思考题

学完本文,来试试回答这些问题:

1️⃣ ASSUME伪指令的作用是什么?它是否实际给段寄存器赋值?

答: ASSUME的作用是告诉汇编程序建立段寄存器与逻辑段之间的对应关系,但它并没有实际给段寄存器赋值 。除了CS由系统自动初始化外,DS、ES、SS都需要在代码段中手动赋值,且不能直接用立即数(如MOV DS, DATA是错误的),必须通过AX中转(MOV AX, DATAMOV DS, AX)。

2️⃣ 小端模式下,DW 3344H在内存中如何存储?

答: 小端模式低字节在低地址。3344H的低字节是44H,高字节是33H,所以在内存中存储为44H 33H。低地址存放低字节,高地址存放高字节。

3️⃣ DOS功能调用09H显示字符串时,对字符串有什么特殊要求?

答: 字符串必须以$符号(ASCII码24H)结束。DOS从指定地址开始逐个字符显示,直到遇到$为止。如果忘记添加结束符,程序会继续显示内存中$之前的所有内容,导致输出混乱。

4️⃣ 宏和子程序各适用于什么场景?

答: 宏适用于代码量小、调用频繁、对执行速度要求高的场景,因为宏在汇编时展开,没有调用返回的开销,但会增加代码体积。子程序适用于代码量大、需要节省空间的场景,因为子程序只有一份代码,但有调用和返回的时间开销。

5️⃣ 为什么字符串输入缓冲区的第0字节是最大字符数,第1字节是实际字符数?

答: 这是DOS系统功能调用0AH的固定格式要求。第0字节由程序员设置,告诉DOS缓冲区最多能容纳多少个字符(防止溢出);第1字节由DOS在输入完成后自动填写,记录用户实际输入了多少个字符,方便程序后续处理。从第2字节开始才是真正的字符串内容。

📚 学习总结与建议

重点掌握内容

  1. 完整的汇编源程序结构:段定义、段寄存器初始化、程序入口和出口
  2. 常用伪指令:DB/DW/DD、SEGMENT/ENDS、PROC/ENDP、EQU、ORG
  3. DOS系统功能调用:01H(字符输入)、02H(字符输出)、09H(字符串输出)、0AH(字符串输入)、4CH(返回DOS)
  4. 四种基本程序结构:顺序、循环、分支、子程序

学习建议

  • 从完整程序入手:先看懂并运行几个完整的示例程序,了解整体结构,再逐步深入学习各个部分
  • 多写多调试 :使用DEBUG或TD工具单步执行程序,观察寄存器和内存的变化,这是学好汇编最有效的方法
  • 注意细节 :小端模式、字符串的$结束符、输入缓冲区格式、段寄存器通过AX中转赋值......这些细节决定了程序能不能正确运行
  • 结合硬件知识:汇编与硬件密切相关,结合计算机组成原理的知识学习,理解会更深刻

✅ 本节完

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

相关推荐
qq_白羊座1 小时前
GitLab CI + Jenkins 双流水线模式Jenkins 端实现
java·开发语言
一条泥憨鱼1 小时前
深入理解Java反射(超详细)
java·开发语言·spring·mybatis·反射
sycmancia1 小时前
Qt——Qt中的调色板
开发语言·qt
J-query1 小时前
修改AndroidStudio的Boot Java Runtime for the IDE后,AndroidStudio启动就报错
java·开发语言·ide·android studio
雪度娃娃1 小时前
ASIO异步通信——服务器网络层和逻辑层设计
开发语言·网络·c++·php
Zhang~Ling1 小时前
C++ 多态完全指南:虚函数、重写、虚表与动态绑定深度解析
开发语言·c++
不负岁月无痕1 小时前
STL-- C++ list类 模拟实现
开发语言·c++·list
JSON_L1 小时前
PHP 高精度计算完全指南:彻底解决浮点数精度丢失
开发语言·php
江屿风2 小时前
C++OJ题经验总结(竞赛)3
开发语言·c++·笔记·算法