资讯动态

8086字符串匹配实验:禁用串操作指令,底层逻辑反而更清晰

发布时间:2026/10/22 1:07:57 来源:尧图企业网站定制
简介面向微机原理与8086汇编语言学习者的字符匹配实验完整报告覆盖字符串搜索子串的完整程序设计流程。资源围绕以00h结尾的主字符串str与子串substr在禁止使用串操作指令的约束下通过变址寄存器与条件跳转实现字符逐一比较、失败计数与指针回退并将匹配结果写入found与pos单元。报告包含算法框图、主要循环体设计、完整汇编代码及注释并给出匹配成功与匹配失败两组测试样例在EMU8086与DOS环境下双重验证运行结果最后附实验心得与排错思路适合作为课程设计或上机实验的参考。压缩包仅含1个docx文档大小839KB已有1700余人学习尤其适合需要完成同类字符串匹配任务或理解汇编底层逻辑的读者。1. 8086字符串匹配实验禁掉串操作指令后逻辑反而更清楚很多人觉得8086汇编里的字符串匹配最省事的方式是直接用串操作指令比如cmpsb、scasb一条搞定。但这个实验的考题偏偏把这条路堵死了题目明确禁止使用任何串操作指令等于逼你回到最原始的字符比较、指针递增和循环回退。我第一次完整做完这个实验时最大的收获不是背会了一段代码而是把匹配算法里两个最关键的判断顺序记住了相等分支要先检查子串结束符不等分支要先检查主串结束符。顺序反了程序要么漏匹配要么给出一个莫名其妙的位置。这篇按我自己的完成顺序来写从题目拆解、寄存器分配、核心循环体到EMU8086和DOS双重调试再附上调试时踩过的五个坑。正在做微机原理实验、或者想彻底理解8086字符匹配底层逻辑的人可以照着这条路走一遍。2. 动手写码前先定三件事寄存器、指针复位、循环出口2.1 题目里的三个隐藏考点题目表面很简单在str里搜substr找到就把found置为ffh并把位置写进pos找不到就把found置为00h。但真正决定程序怎么写的是三个容易被忽略的约束。第一个是长度约束str小于200hsubstr小于80h。200h是512字节相比8086一个段64KB的寻址范围这个长度保证str和substr都能用16位偏移直接表示不需要考虑跨段寻址。这意味着你完全可以用si、di这样的变址寄存器直接指向字符单元地址计算保持在16位范围内程序结构能简单很多。如果长度没有这个约束就必须引入32位偏移或者分段处理复杂度会完全不一样。第二个约束是双结束符主串和子串都以00h结束。这个约束决定了循环的出口条件。你没法像高级语言那样先调一个strlen算出长度再按长度做循环只能在比较的过程中实时检查当前字符是不是00h。换句话说结束符既是字符串的边界也是循环的终止条件。这也是为什么相等之后先查子串结束符、不等之后先查主串结束符这两个判断如此重要。第三个约束是禁止串操作指令。movsb、cmpsb、scasb这些硬件加速指令全部不能用相当于把复制、比较、扫描的捷径关掉逼你自己用mov、cmp、jmp搭出这三件事。这其实是一个很好的训练当你不能用cmpsb时字符比较就必须显式地读出内存、写入寄存器、再执行比较每一步都在眼前算法逻辑反而更清楚。这三个约束放在一起其实已经暗示了程序的基本骨架一个外层循环负责从主串的不同位置开始尝试一个内层循环负责逐字符比较。外层循环的出口是主串结束内层循环的出口是子串结束或字符不匹配。想清楚这一层后面写代码只是把骨架填实而已。2.2 先人工推演一遍为什么匹配位置要从0数拿到题我先没写代码而是把实验给出的测试字符串抄下来手工跑了一遍。str是bbbbubbupbbbupt*bbuptsubstr是bbupt。位置编号从0开始也就是题目里说的以str首地址为0参考点。第0位是*和子串第一个字符b比较不等那么从第1位开始尝试第1位是b、第2位是b、第3位是*子串第三个字符期望是u不等。继续试第4位b b u前三个字符b b u对上第四个期望p实际是不等。第8位b b u p第四个字符p对上第五个期望t实际是不等。真正完全匹配出现在第20位b b u p t五个字符全部相等而且再往后的结束符也正好可以和子串的结束符对上。所以人工推演的预期结果应该是foundffhpos20。这一步看起来笨但非常值。因为在推演过程中你会发现一个容易漏掉的细节字符相等之后不能立刻判断匹配成功要先看子串是不是已经走到结束符而字符不等之后也不能立刻放弃要先看主串是不是已经走到结束符。这两个判断的先后顺序如果不通过实际例子推一遍很容易在写代码时被忽略。我当时就是在这个点上翻了一次车后面避坑章节会细说。另外一个容易忽略的点是匹配位置是从0开始数的不是从1。题目特意强调以str首地址为0参考点就是为了把位置语义定死。如果你心里默认第一个字符是1号最后pos就会比预期大1而且这个问题在结果上非常隐蔽不拿纸面推演对比根本发现不了。2.3 寄存器分配谁管指针谁管计数写8086程序之前我习惯先把寄存器分工写在注释里不然后面一调就容易乱。这个程序的四个关键角色是主串指针、子串指针、失败计数、暂存字符。我用的分配方案是si只管str的当前字符di只管substr的当前字符cx作为失败计数同时也承担匹配起始位置的记录al用来暂存从主串取出来的当前字符。之所以用si和di是因为它们是变址寄存器在字符串访问里语义最顺cx作为计数器是8086的惯例而且后面如果要接loop指令也顺手al是累加器的低位做单字节比较存取时最常规。把分工定下来之后代码里每个寄存器的职责就固定了调试时看寄存器值就能知道程序走到哪一步。这里有个小经验凡是带位置统计的程序计数基准一定要在注释第一行写清楚。我在这份代码里写的是CX从0开始匹配位置以str首地址为0参考点后面所有逻辑都按这个基准走才没有出现位置差一的毛病。寄存器分工大致是这样寄存器职责说明si主串当前字符指针每轮比较开始时映射到strcxdi子串当前字符指针每次失败后回到substr起始cx失败计数 / 匹配起始位置初始为0成功后直接写入posal暂存主串当前字符与子串字符比较这张表也可以理解为整个算法的状态变量。调试的时候只要盯住cx的值就知道当前是从主串第几位开始尝试一旦匹配成功pos单元里的值就应该等于cx不需要额外换算。所以寄存器分配不是随便选的而是为了让计数和位置两个概念在一个变量上统一省掉最后一步换算也少一个出错点。3. 核心循环体拆解相等、不等、结束符三条路径怎么走3.1 字符比较与相等处理内层循环每次做两件事取字符、比较。取字符用的是mov al, [si]把主串当前字符读进al比较用的是cmp al, [di]把al和子串当前字符做比较。这两条指令合起来就是一个最基本的字符比较单元也是整个实验里被反复执行次数最多的部分。比较完之后如果相等进入相等处理。容易犯的错误是在这里直接inc si、inc di继续比下一个字符而忘了子串是否已经匹配完整这个问题。所以正确的顺序是先检查di指向的字符是不是00h如果是说明substr从开头到结尾的全部字符都已经匹配上整个匹配成功如果不是才允许inc si、inc di继续往后比。这个检查和子串的结束符机制是配套的因为substr以00h结束当比较到了结束符位置就表示没有更多字符需要验证了。我用伪代码表示就是比较相等 → 判断di是否为00h → 是则匹配成功否则双指针前移继续循环。注意这个判断与字符相等判断是两个独立步骤不能合并。合并之后会出现一种隐蔽的bug主串里某个字符恰好是00h时可能被误当成子串结束符直接判定成功而ptr类型又导致汇编器报警后面避坑章节会展开。3.2 不相等处理与失败计数字符比较不相等说明当前起始位置匹配失败。这时第一件事不是急着inc cx换下一个位置而是检查主串si指向的字符是不是00h。如果已经是00h说明主串已经全部扫描完整个匹配失败found置00h程序准备退出如果不是00h说明主串后面还有字符可以把起始位置往后挪一位再试一次。挪动这一步就是题目里失败计数值加一并映射到指针的具体实现。我用的做法是inc cx然后无条件跳回外层循环入口在那里重新执行lea si, str和add si, cx把主串指针映射到strcx的位置同时di重新lea到substr起始。这样下一轮比较就从主串的第cx位重新开始。这个设计的巧妙之处在于cx一边记录失败次数一边直接充当本轮匹配的起始偏移量最后匹配成功时不用做任何换算直接把cx存入pos就是答案。代价也在这里每次失败都要重新计算一次地址lea、add各执行一遍比直接在原si基础上加一多几条指令。但考虑到8086上这些指令本身开销不大而且可读性和调试方便程度大幅提升我认为这个取舍值得。如果你更在意速度也可以改成不等时直接inc si并让di回到substr开头但那样就必须额外用一个寄存器记录本轮起始位置寄存器一紧张很容易把自己绕晕。3.3 核心代码段完整匹配循环把上面两条路径落到汇编核心循环长这样。这是按MASM/TASM风格写的EMU8086可以直接跑; 核心匹配循环cx 是匹配起始位置0 表示主串首地址为参考点 MATCH: LEA SI, STR ; si 主串起始地址 ADD SI, CX ; 加上 cx 后指向本轮起始字符 LEA DI, SUBSTR ; di 子串起始地址 LOOP_CP: MOV AL, [SI] ; 取出主串当前字符 CMP AL, [DI] ; 与子串当前字符比较 JNE MISMATCH ; 不等 - 走失败路径 CMP BYTE PTR [DI], 00H ; 相等先看子串是否已到结束符 JE MATCH_OK ; 子串结束 - 全部匹配成功 INC SI ; 继续比较下一位 INC DI JMP LOOP_CP MISMATCH: CMP BYTE PTR [SI], 00H ; 不等先看主串是否已到结束符 JE MATCH_FAIL ; 主串结束 - 匹配失败 INC CX ; 失败计数加一相当于从下一位重试 JMP MATCH MATCH_OK: MOV BYTE PTR FOUND, 0FFH ; 置成功标志 MOV WORD PTR POS, CX ; 保存匹配位置 JMP DONE MATCH_FAIL: MOV BYTE PTR FOUND, 00H ; 置失败标志 DONE: MOV AX, 4C00H INT 21H ; 返回 DOS逻辑说明整个程序只有一个外层循环MATCH和一个内层循环LOOP_CP。外层循环负责选起始位置内层循环负责逐个字符对比。MATCH_OK和MATCH_FAIL是两个出口分别对应找到和没找到。成功时cx的值就是匹配位置直接写入pos失败时found清零。整体流程就是从主串第0位开始尝试失败则cx加一换下一位直到主串结束或匹配成功。参数说明byte ptr和word ptr不是可选项而是必需的类型限定符。cmp byte ptr [di], 00h告诉汇编器这是一个字节比较mov word ptr pos, cx告诉汇编器pos是16位变量。8086的汇编器对内存操作数的类型推断依赖上下文如果变量定义和写法不一致有的环境会直接报错有的环境会用默认类型比较产生隐蔽错误。宁可每次都写清楚也不要省这几个字符。指令选择的小说明lea si, str这类地址装载也可以用mov si, offset str替代两者在这个程序里效果一样。我习惯用lea理由是这样更直观地表达我要的是地址而不是值而且在程序结构变化时不容易写错。如果你用的是EMU8086mov si, offset str同样支持按个人习惯选一个就好。4. 调试与验证匹配成功和匹配失败两组用例的完整检查4.1 匹配成功案例pos20是怎么验出来的程序写完后第一组测试用的是实验给出的成功数据strbbbbubbupbbbupt*bbuptsubstrbbupt。按前面人工推演预期结果是foundffh、pos20。在EMU8086里我用的步骤是先单步执行到MATCH_OK标签看cx寄存器值是否为20再查看found单元是否为ffhpos单元是否被写入20。单步的时候有一个值得养成的习惯在每个MATCH循环入口看一次cx和si的对应关系确认si确实等于str首地址加cx。这样不是等最终结果而是每个起始位置都验证一遍映射是否正确。这组数据还有一个值得注意的地方匹配成功前程序经历了多次失败尝试。cx从0递增到20每一次递增都代表一次从该位置开始匹配失败的记录。所以在看寄存器时cx的变化本身就在讲一个故事一开始在位置0失败位置1失败位置2失败……直到位置20才成功。这个递增过程能帮你快速判断整个算法的主循环是否真的在工作以及指针复位逻辑是否每次都把si拉回到了正确的位置。之后在DOS下跑同一份程序运行结果一致。这里的一致性不只是最终found和pos也包括程序能够正常结束、回到DOS提示符。这说明中断调用和程序出口也没问题不是只碰巧匹配对了。4.2 匹配失败案例found00h的判定路径第二组测试是失败数据strbbbbubbupsubstrbbupt。这组数据里主串只有片段bb、bbu、bbup没有完整的bbupt预期结果是found00h。调试时的重点已经不是结果对不对而是程序走了多久才判定失败。应该是某次在MISMATCH路径里检测到主串si指向00h才进入MATCH_FAIL。所以我在MISMATCH标签设了一个观察点确认程序确实经历了多次不等之后在某次si指向00h时进入match_fail的完整流程而不是因为某个中间状态的巧合提前退出。这组数据也值得人工推演一遍位置1开始的b b第三个字符不等于u位置4开始的b b u第四个字符不等于p位置8开始的b b u p第五个字符不等于t主串结尾是*也就是位置12的字符。当cx走到13时主串已经到结束符程序判定失败。整个过程里每次都是部分匹配后在第3到第5个字符处断开没有一次完整对上。这种部分匹配多次、但最终失败的用例比完全不相干的字符串更有测试价值因为它能暴露指针复位的错误。这组数据同样在EMU8086和DOS两个环境各跑一遍结果一致。两条路径都验证过程序才算真正闭环。4.3 为什么两种环境结果一致才算过有人会问EMU8086能跑不就行了为什么还要放到DOS里再跑一遍我的经验是EMU8086是模拟器它对段寄存器和内存布局的处理比真实DOS环境宽容。程序入口如果没有初始化数据段EMU8086可能仍然把数据读对了但真实DOS下段寄存器指向的内容完全不可预期取出来的字符串就会是乱码匹配结果自然对不上。所以我给自己定的标准是两份结果一致才算过。这份资源的调试思路也是这个——先在EMU8086里单步检查寄存器确认逻辑正确再放到DOS下整体运行确认环境兼容性。如果你手头没有DOS环境用EMU8086配合debug查看内存也行但至少要模拟重新加载程序后数据段重新初始化这个过程避免因为模拟器状态残留而误判。另外调试时要把寄存器窗口利用起来。EMU8086单步模式下看cx的变化是最直接的定位手段当cx从0递增到20每一个取值对应一次失败尝试。如果某两次之间si的地址没有按str旧cx重新映射说明指针复位有问题问题出在MATCH循环体而不是字符比较本身。这种用寄存器值反推程序路径的习惯对汇编调试特别有用。5. 避坑这五个问题我调试时都撞上过5.1 现象一匹配成功但pos比预期差一个字节第一次调通程序时匹配是找到了pos却比人工推演的值大1。比如预期pos20程序给的是21。原因我在初始化时把cx设成了1本意是第一个字符位置是1但题目要求以str首地址为0参考点位置从0开始数。这样每一次失败后cx的基准整体偏移了一位最后写进pos的自然就多1。这属于位置计数基准没统一程序逻辑本身没问题纯是语义理解偏差。解决强制统一基准。起始时xor cx, cx让cx从0开始并在代码开头注释里写明参考点主串首地址位置从0计。从那以后我写任何带位置输出的循环第一行注释一定是计数基准防止过两天回来看不懂。5.2 现象二空子串会给出一个莫名其妙的位置用substr为空串第一个字节就是00h做测试时程序没有按预期立刻返回foundffh、pos0而是一直在主串里扫描最后把主串的长度当成pos返回。原因内层循环的出口之一依赖字符相等且di指向00h来判定成功。空串的第一个字符就是00h程序逻辑上确实是在主串末尾遇到结束符时才完成这个条件位置自然就错了。严格说这不是死循环但结果完全错误。解决在主循环前加一个特判如果substr第一个字节是00h直接置foundffh、pos0跳过整个匹配流程。这个特判也顺带回答了空子串怎么算这个边界问题。题目没明确约定但程序至少不能给出一个莫名其妙的位置。5.3 现象三EMU8086能跑真实DOS下输出全是乱码程序在EMU8086里运行正常换到DOS环境后字符串匹配结果完全不对found和pos看起来像随机值。原因程序入口没有显式初始化数据段。EMU8086模拟器的默认数据段环境恰好能读到str数据真实DOS环境下ds的值不可预期lea si, str得到的是段内偏移但没有正确的ds取出来的字符自然不对。解决在程序入口加上初始化代码先把数据段基址装载到ds再开始字符串匹配。从那以后我每次写可执行程序的第一段固定动作都是先初始化段寄存器再写业务逻辑。这个顺序不能颠倒否则在模拟器里可能侥幸能跑换到真实环境就翻车。5.4 现象四内存操作数没写byte ptr汇编直接报错写cmp [di], 00h时汇编器提示操作数大小不明。原因是[di]是寄存器间接寻址汇编器无法从写法上推断操作数是一个字节还是一个字。原因8086汇编里的内存操作数需要类型限定符byte ptr表示字节访问word ptr表示字访问。我在调试过程中为省事删过类型限定符结果编译不过。解决用cmp byte ptr [di], 00h保持原写法或者改成mov bl, [di]; cmp al, bl。两种方式都能过编译但前一种少用寄存器。关键是每次写内存比较都要检查有没有带上类型限定符。5.5 现象五失败后指针没复位漏掉大量位置还有一个隐蔽问题一开始我以为字符不等时只要inc si就能从主串下一位继续于是省掉了外层循环的lea和add结果某些测试数据下漏匹配。比如strababcsubstrabc时从位置0匹配到ab后就失败只inc si会变成从b开始继续实际上应该从位置1重新比较这里就漏掉了一些正确起始位置。原因内层循环已经把si和di同步向后推进了。如果失败后只在原有si基础上加一si指向的是本轮尝试失败时比较到的那一位再加一而不是本轮起始位置再加一。起始位置只有cx精确记录了必须回到str首地址重新映射。解决任何时候失败都老老实实走MATCH入口重新执行lea si, str和add si, cx。宁可多算一次地址也不能省掉指针复位。这个坑的教训是循环里如果同时有多个指针在变失败回退时必须让所有指针都恢复到本轮起始状态只恢复一个是不够的。6. 一个让我少走弯路的习惯pos值先用纸笔算一遍这个实验给我留下最深的一个习惯是写代码之前先把测试用例的手工推演当作程序的一部分来做。具体做法是把str按字符编号列出来每个字符上面标下标把substr写在下面然后逐位对齐尝试找到第一个完全重叠的位置记下这个下标的预期值。程序跑通后用EMU8086单步到MATCH_OK检查cx是否等于纸面推演的pos。这个习惯帮我省掉的调试时间非常可观。汇编程序的输出位置错一位排查起来往往要花十几分钟看寄存器而纸面推演两分钟就能给出答案。当我发现程序给出的pos和手算不一致时我不用猜是哪里错了直接知道问题出在计数基准或者指针复位这两个方向之一。这比在寄存器窗口里漫无目的地单步快了太多。再补充一个配套习惯写完核心循环后在注释里把两个判断顺序写出来相等→先查子串结束不等→先查主串结束。这两句话看着简单但如果不是在实验里反复踩坑很难体会它们为什么这么重要。它们的本质是回答两个问题匹配什么时候算成功匹配什么时候算失败。这两个出口只要有一个判断含糊程序的边界行为就会变得不可预测。从那以后我每次写涉及字符串或数组匹配的循环都会强制先做纸面推演把计数基准写进注释第一行再开始写指令。回头再看这份实验报告最值钱的部分反而不是那几十行汇编而是这个从推演到调试的完整方法。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑