
🌈 say-fall:个人主页 🚀 专栏:《手把手教你学会C++》 | 《系统深入Linux操作系统》 | 《数据结构与算法》 | 《小游戏与项目》 💪 格言:做好你自己,才能吸引更多的人,与他们共赢,这才是最好的成长方式。
📝 前言
学了8086的一大堆指令------MOV、ADD、CMP、JMP......你可能会想:指令我都背得差不多了,可是怎么才能把它们组织成一个真正能跑起来的程序 呢?确实,知道每条指令是什么意思和写出完整的汇编程序之间,还隔着一道重要的门槛:程序结构设计。
在高级语言中,我们随手写个print("Hello")就能输出,但在汇编里,你得自己定义数据段、初始化段寄存器、通过中断调用来实现输入输出......每一个细节都由你掌控,这既是汇编的难点,也是它最迷人的地方------你能精确地控制计算机的每一个行为。
本文将从零开始,带你系统地学习汇编程序设计的完整知识体系,包括伪指令、系统功能调用、基本程序结构,最后通过一个完整的实例把所有知识串联起来。
通过本文,你将掌握:
| 技能 | 应用场景 |
|---|---|
| 汇编源程序的完整结构 | 编写可编译运行的.asm程序 |
| 常用伪指令(DB/DW/EQU/SEGMENT等) | 定义数据、段、常量 |
| DOS/BIOS系统功能调用 | 实现键盘输入、屏幕输出 |
| 四种基本程序结构 | 顺序、循环、分支、子程序 |
| 宏定义与子程序 | 代码复用和模块化编程 |
📌 前置知识: 掌握8086的六大类指令(数据传送、算术运算、逻辑运算和移位、串操作、程序控制),了解段寄存器和内存寻址的基本概念。
文章目录
-
- [📝 前言](#📝 前言)
- [一、🔧 汇编语言程序设计基础](#一、🔧 汇编语言程序设计基础)
-
- [1️⃣ 汇编源程序与汇编程序](#1️⃣ 汇编源程序与汇编程序)
- [2️⃣ 汇编语言语句类型](#2️⃣ 汇编语言语句类型)
- [3️⃣ 汇编语言语句格式](#3️⃣ 汇编语言语句格式)
- [4️⃣ 操作数的类型](#4️⃣ 操作数的类型)
- [5️⃣ 常用运算符](#5️⃣ 常用运算符)
- [二、📐 伪指令详解](#二、📐 伪指令详解)
-
- [1️⃣ 数据定义伪指令](#1️⃣ 数据定义伪指令)
- [2️⃣ 符号定义伪指令EQU](#2️⃣ 符号定义伪指令EQU)
- [3️⃣ 段定义伪指令](#3️⃣ 段定义伪指令)
- [4️⃣ 完整的汇编源程序结构](#4️⃣ 完整的汇编源程序结构)
- [5️⃣ 结束伪指令END](#5️⃣ 结束伪指令END)
- [6️⃣ 过程定义伪指令](#6️⃣ 过程定义伪指令)
- [7️⃣ 宏命令伪指令](#7️⃣ 宏命令伪指令)
- [8️⃣ ORG伪指令](#8️⃣ ORG伪指令)
- [三、💻 系统功能调用](#三、💻 系统功能调用)
-
- [1️⃣ DOS/BIOS功能调用概述](#1️⃣ DOS/BIOS功能调用概述)
- [2️⃣ 常用DOS功能调用(INT 21H)](#2️⃣ 常用DOS功能调用(INT 21H))
- [3️⃣ 常用BIOS功能调用](#3️⃣ 常用BIOS功能调用)
-
- [键盘状态检验(INT 16H,功能号01H)](#键盘状态检验(INT 16H,功能号01H))
- [四、🏗️ 汇编语言程序设计基本方法](#四、🏗️ 汇编语言程序设计基本方法)
- [五、🎯 完整程序示例:输入字符串并显示](#五、🎯 完整程序示例:输入字符串并显示)
- [六、🤔 几个思考题](#六、🤔 几个思考题)
-
- [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之上,提供更高级功能 | 更易使用,功能更丰富 |
调用的基本步骤:
- 将调用参数装入指定寄存器
- 将功能号送入
AH寄存器 - 执行中断指令(如
INT 21H) - 检查返回参数
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, DATA → MOV 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字节开始才是真正的字符串内容。
📚 学习总结与建议
重点掌握内容
- 完整的汇编源程序结构:段定义、段寄存器初始化、程序入口和出口
- 常用伪指令:DB/DW/DD、SEGMENT/ENDS、PROC/ENDP、EQU、ORG
- DOS系统功能调用:01H(字符输入)、02H(字符输出)、09H(字符串输出)、0AH(字符串输入)、4CH(返回DOS)
- 四种基本程序结构:顺序、循环、分支、子程序
学习建议
- 从完整程序入手:先看懂并运行几个完整的示例程序,了解整体结构,再逐步深入学习各个部分
- 多写多调试 :使用DEBUG或TD工具单步执行程序,观察寄存器和内存的变化,这是学好汇编最有效的方法
- 注意细节 :小端模式、字符串的
$结束符、输入缓冲区格式、段寄存器通过AX中转赋值......这些细节决定了程序能不能正确运行 - 结合硬件知识:汇编与硬件密切相关,结合计算机组成原理的知识学习,理解会更深刻
✅ 本节完
📝 作者:say-fall | 编辑:say-fall | 🌟 原创不易,如果对你有帮助,记得 👍 点赞 + ⭐ 收藏 哦!