say-fall个人主页专栏《手把手教你学会C》 | 《系统深入Linux操作系统》 | 《数据结构与算法》 | 《小游戏与项目》格言做好你自己才能吸引更多的人与他们共赢这才是最好的成长方式。 前言学了8086的一大堆指令——MOV、ADD、CMP、JMP……你可能会想指令我都背得差不多了可是怎么才能把它们组织成一个真正能跑起来的程序呢确实知道每条指令是什么意思和写出完整的汇编程序之间还隔着一道重要的门槛程序结构设计。在高级语言中我们随手写个print(Hello)就能输出但在汇编里你得自己定义数据段、初始化段寄存器、通过中断调用来实现输入输出……每一个细节都由你掌控这既是汇编的难点也是它最迷人的地方——你能精确地控制计算机的每一个行为。本文将从零开始带你系统地学习汇编程序设计的完整知识体系包括伪指令、系统功能调用、基本程序结构最后通过一个完整的实例把所有知识串联起来。通过本文你将掌握技能应用场景汇编源程序的完整结构编写可编译运行的.asm程序常用伪指令DB/DW/EQU/SEGMENT等定义数据、段、常量DOS/BIOS系统功能调用实现键盘输入、屏幕输出四种基本程序结构顺序、循环、分支、子程序宏定义与子程序代码复用和模块化编程 前置知识掌握8086的六大类指令数据传送、算术运算、逻辑运算和移位、串操作、程序控制了解段寄存器和内存寻址的基本概念。文章目录 前言一、 汇编语言程序设计基础1️⃣ 汇编源程序与汇编程序汇编语言程序开发的完整流程2️⃣ 汇编语言语句类型3️⃣ 汇编语言语句格式1指令性语句格式2指示性语句伪指令格式4️⃣ 操作数的类型5️⃣ 常用运算符1取值运算符2属性运算符PTR二、 伪指令详解1️⃣ 数据定义伪指令DUP重复操作符和2️⃣ 符号定义伪指令EQU3️⃣ 段定义伪指令基本格式设定段寄存器伪指令ASSUME4️⃣ 完整的汇编源程序结构5️⃣ 结束伪指令END6️⃣ 过程定义伪指令7️⃣ 宏命令伪指令宏与子程序的区别宏的定义和调用8️⃣ ORG伪指令三、 系统功能调用1️⃣ DOS/BIOS功能调用概述2️⃣ 常用DOS功能调用INT 21H1单字符输入功能号01H2字符串输入功能号0AH3单字符显示输出功能号02H4字符串显示输出功能号09H5返回操作系统功能号4CH3️⃣ 常用BIOS功能调用键盘状态检验INT 16H功能号01H四、️ 汇编语言程序设计基本方法1️⃣ 程序设计的基本步骤2️⃣ 四种基本程序结构1顺序结构2循环结构3分支结构4子程序结构五、 完整程序示例输入字符串并显示六、 几个思考题1️⃣ ASSUME伪指令的作用是什么它是否实际给段寄存器赋值2️⃣ 小端模式下DW 3344H在内存中如何存储3️⃣ DOS功能调用09H显示字符串时对字符串有什么特殊要求4️⃣ 宏和子程序各适用于什么场景5️⃣ 为什么字符串输入缓冲区的第0字节是最大字符数第1字节是实际字符数 学习总结与建议重点掌握内容学习建议一、 汇编语言程序设计基础1️⃣ 汇编源程序与汇编程序很多初学者容易混淆这两个概念它们有着本质的区别概念说明扩展名汇编语言源程序程序员编写的文本代码.asm汇编程序将源程序翻译成机器语言的软件如MASM、TASM—目标程序汇编生成的机器语言文件.obj链接程序将目标程序和库文件链接成可执行程序的软件如LINK—可执行程序最终可在操作系统上运行的文件.exe/.com汇编语言程序开发的完整流程编辑(.asm) → 汇编(MASM) → 目标(.obj) → 链接(LINK) → 可执行(.exe) → 调试/运行具体命令MASM myprogram.asm;;汇编生成 .obj 文件 LINK myprogram.obj;;链接生成 .exe 文件 实际开发中推荐使用DOSBox MASM/TASM环境配合TD或DEBUG进行调试2️⃣ 汇编语言语句类型汇编语言源程序由两种基本类型的语句组成语句类型执行主体功能是否生成目标代码指令性语句CPU执行具体操作如MOV、ADD是指示性语句伪指令汇编程序告诉汇编程序如何处理源程序否3️⃣ 汇编语言语句格式1指令性语句格式[标号:] [前缀] 助记符 [操作数], [操作数] [;注释]标号指令的符号地址后面跟冒号:用于程序转移前缀指令前缀如REP、LOCK等助记符操作码如MOV、ADD等操作数寄存器、存储器、常量等注释以分号;开头不影响程序执行2指示性语句伪指令格式[名字] 伪指令助记符 操作数 [, 操作数, ...] [;注释]⚠️ 注意区别指令性语句的标号后面有冒号伪指令的名字后面没有冒号4️⃣ 操作数的类型类型示例说明寄存器AX、BX、ALCPU内部寄存器存储器单元[1200H]、[BX]、DATA1内存中的数据常量1234H、0FFH、‘A’数字常量或字符常量变量DATA1、BUF内存数据区的符号地址具有段值、偏移地址和类型三个属性标号START、NEXT指令的符号地址用于程序转移表达式OFFSET DATA1、30H99H由常量、变量和运算符组成汇编时计算5️⃣ 常用运算符1取值运算符运算符功能示例OFFSET获取偏移地址MOV BX, OFFSET DATA1SEG获取段地址MOV AX, SEG DATA12属性运算符PTR用于临时改变存储器操作数的类型。格式类型 PTR 存储器操作数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伪指令因为每个字符占一个字节。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这些变量在内存中的存储方式小端模式低字节在低地址变量内存内容说明DATA111H 22H 33H 44H字节按顺序存放DATA211H 00H 22H 00H 44H 33H字的低字节在前DATA441H 42H 43H 44H 66H‘A’41H‘B’42H……DUP重复操作符和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定义的符号不能重新定义不占用内存空间。CONSTANT EQU 100 ; 定义常量 VAR EQU 30H99H ; 定义表达式汇编时计算为C9H MESSAGE EQU Hello, World! ; 定义字符串常量3️⃣ 段定义伪指令8086汇编语言采用分段结构程序由代码段、数据段、附加段和堆栈段组成。基本格式段名 SEGMENT [定位类型] [组合类型] [类别] ; 段内内容 段名 ENDS设定段寄存器伪指令ASSUME格式ASSUME 段寄存器名:段名[, 段寄存器名:段名, ...]⚠️ ASSUME只是建立对应关系并没有实际给段寄存器赋值除了CS由系统自动初始化外DS、ES、SS都需要在程序中手动赋值且不能直接用立即数必须通过AX中转4️⃣ 完整的汇编源程序结构; 数据段存放程序中的数据 数据段名 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️⃣ 过程定义伪指令过程名 PROC [NEAR/FAR] ; 过程体 RET ; 必须是最后一条指令 过程名 ENDPNEAR近过程与调用程序在同一代码段FAR远过程与调用程序在不同代码段示例延时子程序; 大约延时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 DELAY7️⃣ 宏命令伪指令宏与子程序的区别特性宏子程序处理时机汇编时展开运行时调用代码大小每次调用都展开代码量大只有一份代码量小执行速度快无调用返回开销慢有调用返回开销参数传递灵活直接替换通过寄存器或堆栈宏的定义和调用; 宏定义将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, AX8️⃣ ORG伪指令格式ORG 偏移地址设置段内程序或变量的起始偏移地址。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 21H1单字符输入功能号01H项目说明功能从键盘输入一个字符回显到屏幕入口参数无出口参数AL 输入字符的ASCII码; 等待用户输入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结束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 ; 输入字符串 ; BUFF1是实际输入的字符数BUFF2是字符串起始地址 MOV AH, 4CH INT 21H CODE ENDS END START3单字符显示输出功能号02H项目说明功能在屏幕上显示一个字符入口参数DL 要显示字符的ASCII码MOV AH, 02H MOV DL, A INT 21H ; 屏幕显示字符A4字符串显示输出功能号09H项目说明功能在屏幕上显示字符串入口参数DS:DX 字符串首地址⚠️ 字符串必须以$ASCII码24H结束否则会一直显示到内存中出现$为止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 START5返回操作系统功能号4CHMOV AH, 4CH INT 21H ; 程序结束返回DOS3️⃣ 常用BIOS功能调用键盘状态检验INT 16H功能号01H项目说明功能检查是否有键按下不等待出口参数ZF0有键按下AX扫描码ASCII码ZF1无键按下示例循环显示信息按任意键退出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顺序结构最简单的结构程序按指令顺序依次执行。; 计算 1 2 3 MOV AL, 1 ADD AL, 2 ADD AL, 3 ; 结果AL 62循环结构由三部分组成初始化 → 循环体 → 循环控制; 计算1到100的和 XOR AX, AX ; 和初始化为0 MOV CX, 100 ; 循环次数 SUM: ADD AX, CX LOOP SUM ; CX-1不为0则继续 ; 结果AX 50503分支结构根据条件执行不同的代码路径。; 比较两个数将较大值存入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子程序结构将独立功能封装为子程序实现代码复用。前面已详细介绍过程定义和调用方法。五、 完整程序示例输入字符串并显示下面是一个完整的可运行程序实现从键盘输入字符串、然后在屏幕上显示出来; ; 功能输入字符串并显示 ; 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, BUFF2 ; 字符串从BUFF2开始 MOV BL, BUFF1 ; 实际输入的字符数 MOV BH, 0 MOV BYTE PTR [BXDX], $ ; 在末尾添加$结束符 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、ORGDOS系统功能调用01H字符输入、02H字符输出、09H字符串输出、0AH字符串输入、4CH返回DOS四种基本程序结构顺序、循环、分支、子程序学习建议从完整程序入手先看懂并运行几个完整的示例程序了解整体结构再逐步深入学习各个部分多写多调试使用DEBUG或TD工具单步执行程序观察寄存器和内存的变化这是学好汇编最有效的方法注意细节小端模式、字符串的$结束符、输入缓冲区格式、段寄存器通过AX中转赋值……这些细节决定了程序能不能正确运行结合硬件知识汇编与硬件密切相关结合计算机组成原理的知识学习理解会更深刻✅ 本节完 作者say-fall | 编辑say-fall | 原创不易如果对你有帮助记得 点赞 ⭐ 收藏哦