简介这是一份微机原理课程中8086汇编语言字符串匹配实验的完整报告面向计算机专业学生及汇编初学者完整展示在禁止使用串操作指令条件下实现子串搜索的算法思路、程序代码与调试过程。报告以主字符串str与子串substr均以00h结束为背景str长度小于200h、substr小于80h详细说明基于si、di指针和cx计数器的字符比较流程包括相等时继续匹配、不等时通过失败计数回退指针重新比较等核心逻辑以及found置ffh/00h和pos记录匹配位置的规则同时给出匹配成功与失败两种测试用例并附EMU86和DOS环境的双重运行结果便于读者对照复现。资源包仅含一个docx文档共839KB除报告正文外还包含完整代码与逐段注释、设计框图、调试截图、问题分析及实验心得非常适合课程设计、上机实验参考或相关考试复习。该资源已有1709人学习下载对想掌握汇编字符处理和算法细节的读者具有较高参考价值。1. 一个以 00H 结尾的字符串如何在 8086 汇编里完成禁用串指令的匹配我拿到这份实验报告的时候第一反应是不太信字符串匹配这种功能不让用 MOVSB、CMPSB、REP 这些串操作指令不等于把最趁手的工具全没收了但翻完题目和调试记录我得承认这个限制反而把问题变纯粹了。题目就三件事主串 str 和子串 substr 都以 00H 结尾长度分别小于 200H 和 80匹配成功把 found 单元写成 FFH、把位置以 str 首地址为 0存进 pos找不到就把 found 清零。它最对口的读者是三类人正在准备微机原理上机实验的学生、想找一份能在 8086 仿真器和 DOS 环境下直接跑通并二次修改的汇编样例的开发者以及那些想复习寄存器和标志位用法、但不想从 Hello World 重新开始的人。抛开课设外壳它本质上是一个纯指针比较加状态跳转的小型程序没有复杂中断没有浮点考验的就是基础。2. 题目拆解与技术栈00H 结尾、无串指令、found/pos 双出口先把这个实验翻译成一串可执行的工程约束。这里至少包含三个关键点字符串没有长度字段、禁止串操作指令、输出标志的格式。它们共同决定了后面所有代码的写法任何一个理解偏了程序跑出来的结果都是错的。2.1 三个硬约束逐条翻译第一个约束是字符串结构。str 和 substr 都以 00H 结尾而不是像高级语言那样在头部放一个长度字段。这意味着程序在比较字符时必须自己边走边看当前字符是不是 00H。00H 是结束标志不能参与字符比较——一旦发现 substr 当前字符是 00H说明子串已经全部比对完本轮匹配成功一旦发现 str 当前字符是 00H 且子串还没比完说明主串已耗尽整体匹配失败。第二个约束是禁用串操作指令。8086 的串操作指令包括 MOVSB/MOVSW、CMPSB/CMPSW、SCASB、LODSB、STOSB以及配套的 REP/REPE/REPNE 前缀。题目把这些全部禁掉意味着字符搬运、比较、扫描都要用 MOV、CMP、INC、DEC、JMP 组合手写。这带来的直接变化是指针的递增、结束判断、失败后的回退全都要明明白白写在代码里任何一个顺序错了结果都不对。比如后面会提到的一个隐蔽问题子串的结束符 00H 到底在哪个分支被检查直接决定程序能不能走到成功出口。第三个约束是输出约定。found 单元是字节匹配成功置 FFH失败置 00Hpos 单元是字保存的是以 str 首地址为 0 的偏移也就是起点下标不是段地址也不是 SI 的完整指针值。这个定义非常关键——如果直接把 SI 存进 pos那就写入了实际内存地址而非基准偏移结果会和题目要求完全不符。我在调试这类程序时见过有人把 SI 的低 16 位直接存进 pos数据看起来差不多但换一个数据段位置就全错了。动手前还要想清楚一个算法层面的问题用暴力匹配还是 KMP题目约束里 str 长度小于 200H512 字节substr 小于 80128 字节总量非常小。在这个规模下做 KMPnext 数组的预处理收益几乎为零反而引入额外的内存和指针管理代码复杂度翻倍。所以报告采用的思路就是最朴素的暴力匹配从主串第 0 位开始逐位挪动起点每轮把子串从头比到尾撞上不相等就把起点右移一位再来。这不是不会优化而是这个约束规模下暴力匹配才是可控、可调试、可验证的最优解。2.2 数据段布局与寄存器分工整个程序用到的数据非常小一个数据段就能装下str 字符串、substr 字符串、found 标志字节、pos 位置字。为方便后面讲解先给出布局单元类型初始值作用STR字节串00H 结尾bbbbubbupbbbupt*bbupt,0主串长度小于 200HSUBSTR字节串00H 结尾bbupt,0子串长度小于 80FOUND字节00H匹配结果标志成功为 FFHPOS字0000H匹配位置以 STR 首地址为 0寄存器分工是这种小程序的灵魂。SI 和 DI 分别担任主串、子串的当前比较指针AL 存放每次从主串取出的字符拿它和 [DI] 指向的子串字符比较。BX 用来保存本轮匹配起点在主串中的偏移每失败一次就加一这也正好对应题目里说的失败计数值加一并映射到指针。为什么不把起点放在 CX 里CX 在常见写法里更常被当作通用计数器拿来存起点偏移容易在层数加深时分不清职责。我用 BX 转存起点偏移语义更清晰编译出来的结果没有差别。2.3 环境选型仿真器与 DOS 实机联调这份资源是 8086 汇编实验典型的运行形态是仿真器里写、DOS 里验。仿真器类工具的好处是单步调试直观能看到寄存器、标志位和内存窗口的变化适合在循环体里逐语句观察 SI/DI 的走向。DOS 环境则贴近真实运行能验证数据段加载、中断调用是否正常——仿真器对段寄存器的初始值通常很宽容真实环境下 DS 必须先显式指向数据段否则程序一开始取字符串就是空指针这个差异后面避坑章会专门说。我调试这类程序的标准流程是先在仿真器里用单步把逻辑跑通确认状态分支都符合预期再把同样的源程序拿到 DOS 环境用汇编器编译、链接跑完整结果。这份实验报告自己也这么做了——匹配成功和匹配失败两个用例两个环境结果一致。双环境验证不是强迫症因为仿真器单步是逻辑正确性验证DOS 运行是环境正确性验证两者覆盖的问题完全不同。3. 算法设计暴力匹配的下标回退模型这章抛开具体指令先讲清楚匹配过程是个什么样的状态机。理解了状态跳转代码就是水到渠成的事情。3.1 为什么禁用串指令后问题变成了指针管理高级语言里写一个 for 循环就能完成字符串查找但在 8086 汇编里每一步都要面对两个指针一个指主串当前字符一个指子串当前字符。所谓匹配就是反复做三件事取字符、比较、根据结果决定两个指针怎么动。禁用串指令后MOVSB 和 CMPSB 都不能用字符搬运、比较就得手动完成。好处是强制你把指针走了几步、为什么走想清楚。比如失败后起点右移一位这句话用高级语言是一行代码用汇编就是三条指令的组合起点寄存器加一、主串指针重定位、子串指针复位。这三条指令缺一条整个匹配就会陷入死循环或者漏匹配。说白了这个实验真正练的不是字符串算法而是指针的回退与重定位能力。3.2 四分支状态转移整个匹配过程可以收敛成一个四分支状态机每一轮循环只会进入以下四种情况之一条件动作下一状态SUBSTR 当前字符等于 00Hfound 置 FFHpos 写入起点偏移成功退出STR 当前字符等于 SUBSTR 当前字符SI、DI 同时后移一位继续比较字符不相等且 STR 当前字符不等于 00H起点偏移加一SI 重定位DI 复位下一轮匹配字符不相等且 STR 当前字符等于 00Hfound 置 00H失败退出这里最需要注意的是顺序先判断子串是否结束再判断字符是否相等。如果顺序反过来会出现一个非常隐蔽的 bug——子串所有字符都匹配完了但程序永远走不到成功分支。原因后面第 5 章专门讲。用一个真实用例手推一遍会更有体感。主串 strbbbbubbupbbbuptbbupt子串 substrbbupt肉眼可见第一次完整匹配出现在主串偏移 14 附近。程序的实际运行轨迹是起点 0 处 与 b 不等起点加 1起点 1 开始 b、b 都相等但第三个字符 与子串第三个字符 u 不等失败起点到 2起点 2、3 都是快速失败起点 4 开始 bbu 与子串前三个字符相等第四位 与 p 不等起点到 5后面几个起点都类似直到起点 14 完整匹配 bbupt检查子串指针指向 00H成功退出。这个过程看起来繁琐但每一步都能在寄存器窗口里验证。3.3 失败计数值如何映射回下一轮起点题目描述里有一句关键的话将失败计数值加一并映射到指针使得下一轮的比较从字符串的第 cx 位重新开始匹配。这句话初看有点绕翻译成代码逻辑就是BXi或CX保存的是当前起点在 str 中的偏移每失败一次就加一下一轮主串指针 SI 不再沿用上一轮的残留值而是重新用str 首地址 起点偏移计算出来。为什么不直接让 SI 加一因为失败时 SI 可能已经走了很远——比如起点 1 那次SI 已经指到第三个字符了直接 SI 加一只会往后挪一位得到的是起点 2 的中间位置而不是起点 2 的起始位置。所以必须维护一个独立的起点偏移寄存器每轮从首地址重新定位。这也是整个程序里最容易写乱的地方很多人第一次写都会把SI 当前位置加一当成起点加一结果漏掉偏移 2、4 这类起点匹配结果时对时错。这个回退模型的核心代码骨架只有三行但承载了整个算法的关键逻辑INC BX ; 起点偏移加一 MOV SI, OFFSET STR ; 主串指针从首地址开始 ADD SI, BX ; 加上新起点偏移定位本轮起点这里的 BX 就相当于题目描述里的 cx。它的值从 0 开始每失败一次加一最终成功时 BX 的值就是 pos 要存的位置。用汇编的视角看这三行指令的含义是失败不是简单地把指针挪一格而是把匹配的起点这个概念显式地存成一个变量每轮重新计算。这个设计保证了任意长度的 str、任意长度的 substr 都能正确遍历。4. 完整代码逐段拆解从 DATA 段到 21H 中断出口前面两章把模型讲透了这章给出完整可运行的汇编程序并逐段说明每段代码在干什么、为什么这么写。代码可以直接在 8086 仿真器或 DOS 环境里汇编链接运行。4.1 数据段与程序骨架程序的数据段只有四个变量代码段也只做三件事初始化 DS、执行匹配、用 INT 21H 返回 DOS。先看完整代码DATA SEGMENT STR DB *bb*bbu*bbup*bbbupt*bbupt, 0 SUBSTR DB bbupt, 0 FOUND DB 00H ; 匹配成功标志初始为未找到 POS DW 0000H ; 匹配位置以 STR 首地址为 0 DATA ENDS CODE SEGMENT ASSUME CS:CODE, DS:DATA START: MOV AX, DATA MOV DS, AX ; 初始化数据段寄存器 XOR BX, BX ; BX 0当前匹配起点在 STR 中的偏移 MOV SI, OFFSET STR ; SI 指向主串首地址 MOV DI, OFFSET SUBSTR ; DI 指向子串首地址 MATCH_LOOP: CMP BYTE PTR [DI], 00H ; 先检查子串是否已经匹配完 JE MATCH_SUCCESS ; 子串结束 整串匹配成功 MOV AL, [SI] ; 取主串当前字符到 AL CMP AL, [DI] ; 与子串当前字符比较 JNE MISMATCH ; 不等则进入失败分支 INC SI ; 相等两个指针都后移一位 INC DI JMP MATCH_LOOP MISMATCH: CMP BYTE PTR [SI], 00H ; 字符不等时检查主串是否结束 JE MATCH_FAIL ; 主串结束 匹配失败 INC BX ; 本轮失败起点右移一位 MOV SI, OFFSET STR ; 重新定位主串指针 ADD SI, BX MOV DI, OFFSET SUBSTR ; 子串从头再比 JMP MATCH_LOOP MATCH_SUCCESS: MOV FOUND, 0FFH ; found 置 FFH MOV POS, BX ; 起点偏移即匹配位置 JMP EXIT MATCH_FAIL: MOV FOUND, 00H ; found 置 00H MOV POS, 0000H ; 位置清零 EXIT: MOV AX, 4C00H INT 21H ; 返回 DOS CODE ENDS END START这段代码的核心是 MATCH_LOOP 循环体。它在每个循环周期内只做四件事检查子串是否结束、取主串字符、与子串字符比较、根据结果决定走成功分支还是失败分支。数据段中的 FOUND 初始为 00H失败分支里再写一次 00H 是为了强调显式置位不依赖初始值。POS 在失败时清零保证程序多次运行结果可预期。几个关键指令的参数含义需要单独说明。CMP BYTE PTR [DI], 00H中的BYTE PTR是必须的因为 8086 汇编里 [DI] 本身没有类型信息立即数 00H 既可以是字节也可以是字不显式声明类型编译器无法确定比较宽度MASM 会直接报operand types do not match。MOV POS, BX是把 BX 这个 16 位寄存器写入 POS 字单元因为题目要求 pos 存的是一个以 0 为基准的偏移量最大不会超过 512字单元完全够用。MOV AX, 4C00H; INT 21H是 DOS 的标准程序退出方式AL 中的 00H 是返回码表示正常结束。4.2 主循环顺序先判子串结束再判字符相等这段代码最值得反复看的是循环开头两行的顺序。我把检查子串结束放在了循环的最前面而不是放在字符比较之后。这个顺序是整个程序能正确找到匹配的关键。考虑这样一个场景子串bbupt最后一个字符 t 刚被比较完这时 DI 已经指向 substr 后面的 00HSI 指向主串下一个字符。如果先取主串字符、再与子串字符比较AL 取出的是主串下一个字符与 [DI] 里的 00H 比较结果必然不相等程序会进入失败分支——但此时子串明明已经完整匹配完了。所以我每轮循环一开始就先看 [DI] 是不是 00H是则直接宣布成功根本不进入字符比较。这个顺序保证了子串刚匹配完这一状态在第一时间被捕获。比较逻辑本身只有三条指令MOV AL, [SI]取主串字符CMP AL, [DI]和子串字符比较JNE MISMATCH不相等就跳失败分支。相等时执行INC SI; INC DI两个指针同时后移继续下一轮。这里还有一个隐含细节主串字符取到 AL子串字符不单独取寄存器直接在 CMP 里用内存操作数比较少用一个寄存器读代码时也更直观。4.3 失败回退分支起点偏移与指针重定位失败分支处理的是字符不等和主串结束两种情况。先检查主串是否结束是因为主串耗尽意味着所有可能的起点都试过了继续循环只会读到 00H 之后的无意义数据。这一步判断放在失败分支里而不是循环开头是因为只有字符不等时才有必要检查主串是否结束——如果字符相等主串是否结束根本不影响本轮进展。主串未结束时执行的代码是这个算法的核心INC BX让起点偏移加一MOV SI, OFFSET STR; ADD SI, BX重定位主串指针到新起点MOV DI, OFFSET SUBSTR让子串指针复位到第一个字符。三条指令缺一不可。如果漏掉ADD SI, BX程序会一直从主串第 0 位开始比陷入死循环如果漏掉MOV DI, OFFSET SUBSTR子串指针会残留在上次失败的中间位置下一次比较会从子串中间开始匹配结果完全错误。这段代码把失败计数和指针映射拆得很清楚BX 负责计数SI/DI 负责指向两者互不干扰。调试时只需要盯住 BX 的值就能知道程序当前在尝试第几个起点盯住 DI 的值就能知道子串已经比到第几个字符。这种一个寄存器管一件事的写法在汇编里比什么都重要。5. 调试与避坑仿真器与 DOS 双环境实测笔记这章把报告里的调试记录和我在复现过程中踩过的坑合并在一起按现象 → 原因 → 解决的格式整理成清单。每一条都来自真实运行不是纸面推演。5.1 两个真实用例的验证结果报告给出了两组测试数据分别覆盖匹配成功和匹配失败两条路径用例主串子串预期结果匹配成功bbbbubbupbbbupt*bbuptbbuptfoundFFHpos14匹配失败bbbbubbupbbuptfound00H我手工验证了第一组用例主串偏移 14 处正好是 bbupt子串全部匹配子串指针指向 00HBX 的值等于 14。第二组主串只到偏移 12最后一个字符是 *字符不等时主串已经指向 00H程序进入失败分支found 置 00H。两个用例在仿真器和 DOS 环境里运行结果一致说明程序没有依赖特定环境的初始状态。调试时的验证手法建议是先把 CX/BX 这个起点偏移寄存器作为观察对象单步执行时确认它从 0 开始、每次失败后加一再观察 SI 每次重定位后指向的字符是否和起点吻合最后等程序 Exit 时看 FOUND 和 POS 两个内存单元的值。三步都正常程序基本就是对的。5.2 避坑记录五个真实踩坑点坑一子串匹配完但 found 始终是 00H现象用 bbupt 去匹配肉眼可见主串里有这个子串但程序跑完 found 还是 00H。单步跟踪发现子串最后一个字符 t 比较完之后程序进了失败分支而不是成功分支。原因当时把结束判断写在了字符比较之后——子串的 00H 是在下一轮循环的字符比较里才被发现的而那一轮比较的双方是主串下一个字符和00H必然不相等于是走了失败分支。解决把CMP BYTE PTR [DI], 00H提到循环开头先判断子串是否走完再决定要不要比较字符。坑二MASM 编译报 operand types do not match现象CMP [DI], 00H编译报错提示操作数类型不匹配。原因8086 汇编里内存操作数没有类型信息编译器无法判断 00H 是字节还是字。解决所有和立即数比较的内存单元都显式写成BYTE PTR [DI]或WORD PTR [POS]类型声明清楚了编译器才能选择正确的比较宽度。坑三仿真器能跑DOS 环境跑出来全是乱码现象同样的程序在 8086 仿真器里结果正常换到 DOS 环境用汇编器编译链接后字符串数据读取完全不对。原因仿真器对段寄存器初始值做了宽容处理DOS 环境下 DS 指向的是 PSP 而不是数据段程序没有显式加载数据段取字符串就取到了无关内存。解决在 START 开头加两行MOV AX, DATA和MOV DS, AX确保 DS 指向自己的数据段。坑四pos 的值大得离谱甚至超过 200H现象匹配成功found 已经置 FFH但 pos 的值看起来像内存地址而不是偏移量。原因代码里直接写了MOV POS, SI把 SI 这个指针值存进去了。SI 在循环过程中存的不是以 str 首地址为 0 的偏移而是实际内存地址。解决单独用 BX 保存起点偏移pos 从这里取不用 SI。找到时MOV POS, BXBX 才是以 0 为基准的下标。坑五主串里同时出现两个相同的子串第一次总是匹配失败现象主串 bbbbubbupbbbupt*bbupt 里有两个 bbupt程序匹配到了位置 22 而不是 14。原因错误地在失败分支里写成了INC SI而不是INC BX; MOV SI, OFFSET STR; ADD SI, BX。直接 INC SI 只在连续失败时把主串指针往后挪一格遇到前面失败过、本轮还要回到正确起点的情况时漏掉了中间某些起点。解决始终以 BX 为唯一起点来源每次失败后完整重算 SI不要在 SI 的残留值上做增量。6. 改造思路把写死的匹配逻辑抽成可复用子程序这份资源当前是一份完整程序数据段、匹配逻辑、DOS 退出全部耦合在一个 START 流程里。如果只把它当成作业交掉那没什么可改的但如果你要在更大的项目里复用它——比如做一个文本扫描器或者连续匹配多组关键词——就得把匹配逻辑抽成子程序用约定的寄存器传参。这一步改造成本极低收益却很高。改造后的接口可以这样设计调用前把主串首地址放入 SI子串首地址放入 DI调用FIND_SUBSTR子程序返回时 AL 为 FOUND 值FFH 或 00HBX 为匹配位置。代码段里先PUSH AX保护现场匹配逻辑用RET返回调用点。数据段里的 STR 和 SUBSTR 字符串保留FOUND 和 POS 可以改成仅用于调试的观测单元甚至删掉因为结果已经通过寄存器返回。如果有多组子串要匹配只需要在调用前修改 DI 指向的目标程序会从主串开头重新查找。再往上走一步可以把比较动作单独抽成一个字节比较宏用宏参数指定两个寄存器这样主循环里CMP AL, [DI]这种指令可以替换成CMP_CHAR SI, DI代码可读性会好很多。注意 8086 的条件跳转是短跳转宏展开后代碼变长时可能超出跳转范围所以抽取时要把跳转标签也一起宏化或者改用JMP中转。这是我实际改造这类程序时最容易翻车的地方。另外两个值得做的扩展是大小写不敏感匹配和全量匹配。大小写不敏感只需要在比较前把 AL 和 [DI] 都过一次大小写归一化——大写字母范围 41H~5AH小写字母 61H~7AH转换方式是对 20H 做异或全量匹配则需要把成功出口改成一个记录位置数组的写入点每找到一组匹配就把 BX 存入一个连续的字数组然后继续从起点 1开始找下一组。这个改造涉及主循环出口的改动建议先在仿真器里用两个用例验证单次匹配逻辑再改循环出口。从那以后我每次写字符串处理程序都会先画一张状态转移表把结束符判断到底先于还是后于字符比较定下来再动手。这个习惯救了我好几次因为汇编里任何一次跳转顺序的调整都要重新想清楚所有分支的触发时机。这份资源其实不需要太多改动就能跑起来真正值钱的是调试记录里那些看似正确但结果错了的瞬间。希望帮到你。本文还有配套的精品资源点击获取