8086汇编实现无串操作指令的字符串匹配:从算法设计到调试避坑

发布时间:2026/10/22 6:42:20
8086汇编实现无串操作指令的字符串匹配:从算法设计到调试避坑 简介面向8086汇编语言学习者与微机原理实验学生的完整实验报告内容围绕无串操作指令的字符串匹配算法展开覆盖题目要求、算法流程图、主循环体设计、完整代码与注释并给出EMU86和DOS双环境下的成功、失败调试结果能直接辅助读者完成同类上机实验。资料为1个docx文档压缩包大小839KB打开即可阅读或修改。报告详细解析了主串与子串均以00h为结束标志时如何用si、di指针与cx计数值配合实现字符比较和位置回溯同时说明匹配成功时found置ffh并将偏移写入pos匹配失败时found置00h便于对照验证。调试章节包含成功与失败两组测试字符串及运行结果截图可帮助排查循环与跳转条件错误实验心得部分还总结了从设计流程图到反复调试的完整思路。该资源目前已有1709人学习适合需要撰写微机原理实验报告或强化汇编编程能力的本科生参考使用。1. 需求先说透这道汇编题卡住的从来不是语法做微机原理课程设计第一次上手 8086 汇编实验很多人会选字符匹配这个题目。理由是它看着结构简单一个主串、一个子串、一个结果标志逻辑上就是逐字符比较而已。但真正动手写代码会发现题目里那句「禁止使用串操作指令」直接把REP CMPSB、SCASB这些现成工具全禁掉了一切都得用MOV、CMP、JMP这种最基础的指令手动拼出来。这个限制恰恰是这道题的核心价值——它逼着你把字符串匹配的底层逻辑彻底想明白而不是调一个指令就完事。我们的目标是实现这样一套完整方案给定以 00h 结尾的主串str长度小于 200h和子串substr长度小于 80h查找子串在主串中首次出现的位置找到则found置为 ffh、pos存位置找不到则found置为 00h并且要求程序能通过 EMU8086 和 DOS 两种环境的上机调试。这套方案适合谁正在做微机原理或汇编语言课设的学生需要一份能从流程图推导到代码实现、再推导到调试记录和答辩话术的完整参考已经写完程序但调试总翻车的同学也可以对照这里的排查思路定位问题。下面从算法设计、代码实现、调试避坑到验收答辩按一份完整实验报告的交付标准来讲。2. 总体设计从数据结构到两层循环的匹配框架2.1 数据段与单元定义先想清楚标志位放在哪写汇编程序最容易犯的错是一上来就写代码寄存器用乱了才知道还缺变量。通常的做法是先把数据段定义完整再设计流程图。内存数据段按以下方式定义DATA SEGMENT STR DB *bb*bbu*bbup*bbbupt*bbupt$ ; 主串实际以 00h 结束 SUBSTR DB bbupt$ ; 子串以 00h 结束 FOUND DB 00h ; 匹配结果标志位 POS DW 0000h ; 匹配位置以 str 首地址为 0 DATA ENDS这里有个容易忽略的细节主串和子串都以 00h 结束但实验报告中经常写成 16 进制数字形式调试时直接看内存不太直观。因此正文里用可打印字符展示真实数据段里在字符串末尾必须补一个00h。上面代码里写的$只是便于 DOS 显示实际判断结束条件用的是 00h。2.2 算法主流程外层控制回退内层逐字符比对核心算法用两层循环实现。外层循环控制主串的比较起点内层循环完成从该起点开始的逐字符比对。设计思路如下外层循环主串起点选择 检查主串当前字符是否为 00h → 是则匹配失败 保存当前主串起点到临时变量 内层循环子串比对 取主串字符 → 取子串字符 → 比较 相等 → 检查子串是否结束已结束则匹配成功 不相等 → 外层起点后移一位重新开始内层匹配位置的计算方式是以主串首地址为 0 参考点因此pos存的是字符下标而非地址偏移。比如主串*bb*bbup*中匹配子串bbup得到的位置是 2因为从 0 开始数第 0 位是*第 1 位是b第 2 位是b。这个「位置从 0 开始」的定义在答题时经常被忽略答辩老师问一下就容易露馅。2.3 寄存器分配SI、DI、CX 各有分工寄存器分配建议这样SI指向主串当前比较起点DI指向子串起始地址CX记录外层失败次数即主串起点向后移动的偏移量。每轮子串比对开始时SI 主串首地址 CX这样内层循环就不需要额外的指针变量。要特别注意CX的双重身份在外层循环它是「失败偏移量」进入内层比对之前要把它的值赋给某个暂存寄存器比如BX内层循环用BX做偏移计算CX保持不变。这个细节如果没处理好内层循环一修改CX外层起点就乱了这是很多学生跑不出正确结果的第一大原因。3. 核心代码实现无串操作指令的逐字符匹配逻辑3.1 内层字符比较等与不等的分支处理程序从外层起点进入内层比对先用MOV把两个字符取到寄存器里再比较。核心代码如下; 内层循环比较主串当前字符与子串当前字符 ; SI指向主串当前位置DI指向子串当前位置 NEXT_CHAR: MOV AL, [SI] ; 取主串当前字符 CMP AL, 00h ; 判断主串是否结束 JE MATCH_FAIL ; 主串先结束 → 匹配失败 MOV BL, [DI] ; 取子串当前字符 CMP AL, BL ; 两个字符比较 JNE MISMATCH ; 不相等 → 处理失败 ; 相等情况检查子串是否结束 CMP BL, 00h ; 子串当前字符是结束符 JE MATCH_OK ; 是 → 后续检查子串下一个字符是否为结束 INC SI INC DI JMP NEXT_CHAR这段代码的逻辑要点在于两个字符相等时不能立刻判定成功必须先确认子串是否已经走到结束符。若子串当前字符是 00h说明子串的全部有效字符都已经匹配完成否则继续移动双指针。判断顺序上先查主串是否结束、再查子串是否结束是因为主串比子串长是常规情况先判断主串结束可以提前终止无意义的比较。3.2 失败回退CX 计数与指针映射匹配不等时的回退是整个算法的精髓。假设主串是*bb*bbup*子串是bbupt第一次从位置 0 开始比对第 0 位是*与子串第 0 位b不等这时主串起点应后移到位置 1。实现方式如下; 不相等处理主串起点后移一位重新开始比对 MISMATCH: INC CX ; 失败次数 1对应主串起点后移 MOV SI, INITIAL_SI ; 恢复SI到主串首地址 ADD SI, CX ; SI 首地址 偏移量 MOV DI, OFFSET SUBSTR ; DI重新指向子串首地址 JMP OUTER_LOOP这里CX既是失败次数也是偏移量但这种二义性并不危险因为每次进入外层循环都要用MOV DI, OFFSET SUBSTR重新初始化子串指针。有一类常见的错误写法是失败回退时只INC SI而不同步更新基准值这样当多重失配发生时比如先比了 3 位再失配INC SI只是从失配位置挪了一位跳过了本该作为起点的位置。用INITIAL_SI CX的映射关系可以保证第 N 轮比较永远从主串第 N 个字符开始。3.3 成功收尾found 与 pos 的赋值逻辑匹配成功的判定条件是子串已结束同时主串当前字符与子串结束符相等其实此时主串字符就是 00h因为子串结束符之前的字符都已匹配成功。这个条件的正确性问题在于如果子串在中间某个位置出现 00h程序必须识别出这是终止标志而不是普通字符。; 匹配成功found置为ffhpos保存匹配位置 MATCH_OK: MOV FOUND, 0FFh MOV AX, INITIAL_SI SUB AX, OFFSET STR ; 计算匹配起始地址与主串首地址的差 MOV POS, AX ; 差值即为匹配位置从0开始 JMP PROGRAM_END位置计算的另一种方式是直接取CX的值因为CX就是当前外层循环的偏移量。两者等价但用地址相减的方式更通用适合后续扩展。用CX更直观这段代码选择地址相减的目的在于强调「pos 是位置不是偏移」这个细节答辩时这一段能体现对概念的理解深度。3.4 主循环完整融合三层跳转的衔接顺序将上面三段代码合并成完整的主程序流程整体框架如下START: MOV AX, DATA MOV DS, AX MOV INITIAL_SI, OFFSET STR ; 保存主串首地址 MOV CX, 0 ; 初始失败次数为0 OUTER_LOOP: MOV SI, INITIAL_SI ADD SI, CX ; 当前比较起点 首地址 偏移 MOV DI, OFFSET SUBSTR JMP NEXT_CHAR这个框架有个明显的优化空间每次外层循环都要重新执行MOV SI, INITIAL_SI和ADD SI, CX如果循环次数多指令开销会稍大。但考虑到主串长度小于 200h最坏情况也就几百次循环用可读性换微小的性能损耗是值得的。如果想优化可以在失败回退时直接INC SI并保存到INITIAL_SI但这样代码的推导关系就不直观了不利于课设答辩时讲清楚。3.5 完整代码的注释规范给答辩留出讲解线索完整代码通常在 80 到 120 行之间。写注释时建议遵循「先说明这段逻辑在做什么、再说明为什么这么做」的原则而不是每行写一个指令含义的翻译。比如下面这段注释就比「将AL存入BL」这种废话有价值; 关键先判断主串结束再判断子串结束 ; 主串结束说明主串剩余长度不足以匹配完整子串调试过程中发现一个有意思的现象很多人在写注释时把每一行的作用都写清楚但整段代码的逻辑闭环没写明白。答辩时老师更可能问的是「为什么失配后要ADD SI, CX而不是INC SI」而不是「MOV AL, [SI]是什么含义」。因此在完整代码中重点注释应该围绕指针回退和结束判断这两个位置展开其余指令简单标注即可。4. 避坑与排查最容易翻车的五个实打实的问题4.1 指针回退错位内层循环改用了 CX现象程序运行结果不稳定有时能匹配成功有时在某个特定字符串下死循环或找不到结果。原因内层字符比较时如果代码里不小心用了CX作为临时比较寄存器比如CMP AL, CL内层循环就会修改CX的值导致外层循环的偏移量失效。等到失配回退时INITIAL_SI CX已经指向了一个错误的位置。更隐蔽的情形是子串比较到一半时CX被某个子程序调用破坏而主程序没有保存恢复。解决内层循环只使用AL、BL、SI、DI这四个寄存器CX在外层循环中只负责维护偏移量进入内层前不依赖它做任何比对。如果实在需要暂存CX就用栈保存PUSH CX和POP CX配对使用。此后每次写完内层循环代码我都会强制检查一遍CX是否只出现在外层循环的偏移计算中。4.2 结束符判断顺序先查子串还是先查主串现象匹配成功时pos位置少了 1或者子串是主串前缀时结果不正确。原因字符相等处理分支里先判断主串结束、再判断子串结束顺序反了。当子串恰好是主串的末尾部分时主串先遇到的 00h 会让程序误判为「主串已结束 → 匹配失败」实际此时子串也只剩结束符了应该判定成功。反过来如果先查子串结束再查主串结束子串结束时主串恰好也结束程序可能漏掉一轮判断。解决子串结束符的判断优先级要高于主串结束符。因为子串长度小于主串长度是题目保证的条件子串先结束是正常情况主串先结束才是异常情况所以要优先处理「正常结束」的分支。这里有一个反直觉的细节两个字符相等时先查子串结束CMP BL, 00h→JE MATCH_OK但如果不相等时先查主串结束CMP AL, 00h→JE MATCH_FAIL两组分支的处理顺序恰好相反。4.3 内存中 00h 与显示字符混淆现象用INT 21H显示字符串时主串和子串末尾的 00h 被$替换但数据段里还是原来的十六进制值导致实际调试结果与代码逻辑不符。原因DOS 的字符串显示函数要求以$结尾但题目要求以 00h 结尾。很多人在数据段里写了DB bbupt$然后程序判断CMP AL, 00h永远不可能触发结束条件进入死循环。格式化输出时用$做显示终止符匹配逻辑用 00h 做终止符两者混用就会出问题。解决数据段字符串用十六进制形式定义比如DB b,b,u,p,t,00h显示功能单独用$拼接另一个字符串变量。调试时可以用 EMU8086 的内存查看窗口确认 00h 是否正确存在于字符串末尾。一个可以偷懒的技巧是汇编器允许DB bbupt,00h这种方式程序运行时两个终止符互不干扰——匹配逻辑读到 00h 停止显示逻辑读到$停止。4.4 位置计算差一地址减法的基准搞错现象匹配成功时pos的值比预期大 1 或小 1。原因一种情况是pos保存的是地址差而不是位置下标当主串从偏移量 1 处开始时地址差会比位置下标大 1。另一种情况是匹配结束后SI已经指向了子串最后一个字符的下一位此时用SI - OFFSET STR计算距离会多算一个字符。根据题目要求「以字符串 str 首地址为 0 参考点」pos应该是第一个匹配字符的下标即 0、1、2 这种值。若用SI - OFFSET STR - 1或者在外层循环进入时用CX直接赋值结果才对得上。解决最可靠的方案是在外层循环每次进入时用MOV AX, CX保存当前偏移量匹配成功后直接MOV POS, AX。如果写地址相减的版本记得在SUB AX, OFFSET STR之后SUB AX, 1或者改用SI - OFFSET STR后减 1 的写法。建议先用CX方案验证程序正确性再改成地址相减方案做对比这样能确认减 1 的偏差是从哪个环节引入的。4.5 死循环与意外跳转无条件跳转用多了现象程序在运行到一半时卡死或者某个分支跳转到了错误地址。原因外层循环结束时JMP OUTER_LOOP无条件跳回但没有重新初始化DI导致第二次循环开始时DI还在上次失败的位置子串比较从错误的地方开始。还有一种情况是JMP NEXT_CHAR跳回后SI已经指向主串结束符之后的内存区域读取越界但汇编不会报错而是把内存里其他数据当字符比较。解决每次无条件跳转前检查跳转目标处的寄存器和指针是否已经恢复初始状态。最简单的自查方法是把程序分段测试单独注释掉内层循环看外层逻辑是否正常再单独注释掉外层循环看内层逻辑是否正常。还有一种玄学但有效的做法在关键跳转处加标签注释比如JMP OUTER_LOOP ; 重新从当前偏移位置开始新一轮比对可以在调试时快速定位跳转链条上哪一环出了问题。5. 实验验证与答辩验收判断程序是否真正可交付5.1 测试用例设计覆盖成功、失败、边界三种场景实验报告写得好不好测试用例设计占一半。你要设计三类用例第一类是匹配成功基础用例主串*bb*bbu*bbup*bbbupt*bbupt子串bbupt预期结果是foundffh、pos16从 0 开始数第 16 个字符是b。这里有个陷阱主串*bb*bbu*bbup*里有一个bbup但后面接的是*不是完整匹配程序应该在扫描到第 16 位时才判定成功不能在第 11 位的bbup处误判。第二类是匹配失败用例主串*bb*bbu*bbup*子串bbupt预期结果是found00h、pos保持不变或清零。这个用例的价值在于失败发生时主串还有剩余字符程序必须完整扫描到最后才能判定失败考验的是外层循环终点的控制。第三类是边界用例主串空字符串直接00h、子串长度为 1、子串等于主串、子串出现在主串开头pos0。特别是「子串出现在开头」这个用例很多程序会漏掉pos0的情况因为它和found初始值 00h 撞了如果程序只在失败时清零pos这个测试能暴露初始化和赋值顺序的问题。5.2 双环境调试记录EMU8086 与 DOS 结果一致性分析实验报告通常要求展示两种环境的运行结果。规范写法是先贴 EMU8086 的寄存器窗口截图或内存窗口截图展示FOUND和POS的值再贴 DOS 下运行的结果截图。分析部分不能只写「结果一致符合要求」要写出对比的依据。我一般会在报告里这样写EMU8086 环境下FOUNDFFh、POS0010h十进制 16DOS 环境下FOUNDFFhPOS0010h两个环境的运行结果完全一致。同时验证了失败用例下两个环境的FOUND00h输出也一致。这说明程序不依赖特定调试器的内存布局逻辑正确且可移植。5.3 答辩演示顺序先讲思路再跑程序答辩时要先把「为什么不用串操作指令」这个问题抛出来主动解释你的替代实现方式。建议按下面的顺序演示先在白板上画出流程图明确外层循环、内层循环、失败回退三条路径接着打开代码逐段讲解重点讲CX偏移量的维护和SI INITIAL_SI CX的映射关系最后运行成功用例和失败用例展示FOUND与POS的变化。针对老师可能会追问「时间复杂度是多少」的问题准备一个标准话术最坏情况是主串长度乘子串长度即 O(m×n)但因为外层起点每次只后移一位不会出现暴力回溯的重复扫描实际效率略优于朴素算法。这道题的输入规模有限性能不重要重要的是算法结构的清晰性。5.4 实验心得从设计到调试的完整复盘心得体会要写「你具体怎么做的」不要写「学会了知识」这种空话。写自己用举例法设计流程先用*bb*bbu*bbup*bbbupt*bbupt这个串手工模拟了一遍匹配过程数出从位置 0 到位置 16 一共比较了多少轮中间bbup假匹配失败几次每次失败后CX的值是多少然后对照这个手算过程设计代码。这种经历比泛泛而谈更有说服力。调试时用一个「观察窗口法」每次循环进入外层时停下来看CX和SI的寄存器值确认它们满足SI INITIAL_SI CX。如果发现偏差回溯上一层跳转指令检查是哪个分支改变了其中一个值而未同步另一个值。这个方法在校验指针回退问题时效率极高。答辩前一天强制自己做一次「闭卷调试」不看参考代码只根据流程图从头写一遍完整程序然后用同一组测试用例跑通。从那以后我每次做实验都强制走一遍这个闭环——先手工模拟一遍数据流再写代码再用寄存器观测验证绝不跳过中间任何一步。希望这套方案能帮你在课设答辩时少踩几个坑。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询