☰
汇编语言字符串匹配实验:不用串操作指令实现子串查找与回溯
2026/10/22 12:12:37 网站建设 项目流程

简介:面向微机原理课程与汇编语言初学者的8086字符匹配实验报告,围绕不使用串操作指令的约束,给出完整的程序设计方案与调试思路。报告首先定义主字符串str与子字符串substr均以00h结尾且长度受限的实验条件,然后从算法流程图与主循环体入手,详细讲解字符比较、相等分支判定子串结束、不相等分支利用CX失败次数重新定位主串指针等核心逻辑;随后给出可直接上机运行的完整汇编源码,每行附有注释,并针对成功和失败两种典型测试样例,在EMU86与DOS环境下分别运行验证,展示found标志与pos位置的设置结果,附有截图和对比说明。实验心得部分总结了寄存器使用、循环控制、边界条件处理等常见问题与调试策略。资源共1个docx文档,大小约839KB,已有1700余人学习查看,适合用作课程设计参考或汇编语言上机实践模板。

1. 字符串匹配实验:从题目要求到完整可运行的汇编代码

第一次拿到这个题目,很多人会觉得“字符串匹配不是有现成的串操作指令吗”。但题目偏偏加了那句话:禁止使用串操作指令。这意味着movsb、cmpsb、rep这些方便工具全部不能用,必须用最原始的mov、cmp、jmp把匹配逻辑手工搭出来。当时我第一版程序在循环里犯了一个典型的指针管理错误,匹配结果总是少算一位,折腾了半个下午才定位到是字符指针没有在失败后正常回退。所以这篇方案的目标很直接:把算法框图、寄存器分配、失败回溯的处理逻辑和调试方法完整走一遍,让读者拿到手能看懂、能照抄、能在 EMU8086 或 DOS 环境里跑出正确结果。适合正在做微机原理课设或正在复习汇编字符串处理的人参考。

2. 总体设计:寄存器分配与数据段布局

2.1 数据结构定义与存储规划

主字符串str以00h结尾,长度小于200h(512 字节),子串substr同样以00h结尾,长度小于80。注意这里长度单位的差异,前者是字节数 512,后者直接是十进制 80,换算成十六进制是50h,两个上限都不算大,所以数据段里开缓冲区时不用贪大。代码里我一般这样定义数据段:

data segment str db '*bb*bbu*bbup*bbbupt*bbupt', 0 substr db 'bbupt', 0 found db 00h pos dw 0000h msg1 db 'Match found!', 0dh, 0ah, '$' msg2 db 'Not found.', 0dh, 0ah, '$' data ends

found用一个字节单元,pos必须用字单元,因为匹配位置的理论最大值是 511,超过一个字节的表示范围。0dh, 0ah是回车换行,$是 DOS 中断INT 21H的 09H 号功能输出字符串时的结束标志。数据段的末尾留作测试输出用的提示信息,调试时能看到程序走到了哪个分支。

2.2 寄存器分配策略

这个程序里寄存器非常紧张,16 位模式下能用的通用寄存器就那么几个,必须提前规划好用途,不然写着写着就冲突了。我采用的分配方案是:

  • SI:主字符串str的当前比较指针
  • DI:子串substr的当前比较指针
  • CX:累计匹配失败次数,同时用于下一轮起始位置的偏移计算
  • AX:暂存从str取出的当前字符
  • BX:保存搜索起点,用于失败后重新定位

这里最容易出错的地方是CX的角色转换。它在成功路径上是剩余字符计数器,在失败路径上变成了失败计数值。很多同学在这两个含义之间切换时搞混,导致指针映射错位。我的习惯是在代码注释里明确标注“此处的 CX 含义是失败次数”,每轮循环进入时都重新确认。

2.3 算法主体流程

整个算法不用串操作指令,核心就是两个指针逐字节走。匹配成功时,SI和DI同步向后移动;匹配失败时,SI从搜索起点向后移动一位,DI退回子串首地址。我一般用自然语言描述这段流程:先从str的某个起点开始,逐个字符和substr的第一个字符比较,如果相等就继续比较下一个,直到子串结束则匹配成功;如果中间某个字符不等,就换下一个起点重新开始。

真正的实现里,关键在于失败后的指针恢复。假设当前SI已经走到了一个不匹配的位置,新一轮比较的起点不能是当前位置,而是要回到本轮比较的起点再加一。这个“回到起点再加一”的操作需要保存每轮比较的起始地址,否则越比越偏。

3. 关键实现:循环体与失败回溯的汇编代码

3.1 核心匹配循环代码

下面是完整可运行的核心匹配逻辑,包含了初始化、字符比较、相等处理、不相等处理四个部分:

assume cs:code, ds:data code segment start: mov ax, data mov ds, ax lea si, str ; SI 指向主字符串 lea di, substr ; DI 指向子串 mov bx, si ; BX 保存本轮搜索起点 xor cx, cx ; CX 清零,作为失败计数值 compare_loop: mov al, [si] ; 取 str 当前字符 cmp al, [di] ; 与 substr 当前字符比较 jne not_equal cmp al, 00h ; 如果 str 当前字符是结束符 je check_sub_end ; 检查子串是否也已结束 inc si inc di jmp compare_loop check_sub_end: cmp byte ptr [di], 00h ; 子串也结束说明匹配成功 jne not_equal equal_found: mov found, 0ffh mov ax, bx ; BX 是本轮搜索起点 sub ax, offset str ; 起点减去 str 首地址得到位置 mov pos, ax jmp display_msg not_equal: cmp byte ptr [si], 00h ; 主字符串是否结束 je not_found inc cx ; 失败计数值加一 mov si, offset str add si, cx ; 新起点 = str首地址 + 失败次数 mov bx, si ; 更新本轮搜索起点 lea di, substr ; DI 回到子串开头 jmp compare_loop not_found: mov found, 00h jmp display_msg

这段代码里需要重点理解的是not_equal分支中的指针映射。每次匹配失败,搜索起点不是简单地在原起点上inc si,而是通过mov si, offset str加add si, cx重新计算。因为CX记录的是累计失败次数,从str首地址偏移CX个字节,恰好就是下一轮应该开始的字符位置。这个设计的好处是即使失败多次,每个字符最多被重新比较一次,时间开销可控。

成功路径上的check_sub_end判断是容易被忽略的边界条件。当主字符串走到00h时,不能直接判定失败,因为子串可能恰好也在这个位置结束,这种情况下两个字符串同时终结就意味着子串实际上是从主串的末尾开始匹配成功的空匹配。

3.2 位置计算与结果输出

匹配成功后,pos单元存的是子串在主字符串中的起始位置,参考点是主字符串首地址 0。上面代码里mov ax, bx后sub ax, offset str的写法,本质上是把绝对地址转换成相对偏移。还有一种等价的写法是:

mov ax, bx mov bx, offset str sub ax, bx

两种写法效果一样,但第二种更直白地表达了“当前位置减去首地址”的含义。最终输出结果我用 DOS 中断展示:

display_msg: cmp found, 0ffh je show_found lea dx, msg2 mov ah, 09h int 21h jmp exit_prog show_found: lea dx, msg1 mov ah, 09h int 21h exit_prog: mov ah, 4ch int 21h

这里INT 21H的09H功能要求DX指向以$结尾的字符串,4CH功能是程序退出返回 DOS。调试阶段如果只想看寄存器和内存变化,输出字符串这一步可以暂时注释掉,直接在模拟器里观察found和pos单元的值。

3.3 不使用串操作指令的替代方案

题目禁止串操作指令,所以movsb、cmpsb、scasb全部不能用,但这不代表只能用笨办法。常见的替代思路是循环内用lodsb和stosb,不过仔细看定义,lodsb和stosb也属于串操作指令,稳妥起见还是全部手动mov加inc。另外一个技巧是用xchg配合寄存器切换来减少代码行数,但可读性会变差。我最终选择全手动方式,因为课设答辩时老师会盯着代码问,写成最直白的形态反而好解释。

4. 调试方法与运行结果对比

4.1 EMU8086 环境下的单步调试

在 EMU8086 里调试这段程序,建议关注三个观察点:SI和DI寄存器的值变化、found单元的内存内容、CX的累计值。单步执行时可以看到每一轮比较后指针是否按预期移动。

我调试时习惯在compare_loop入口处设置断点,每轮循环停下时记录AL的值和两个指针的指向。比较成功的轮次里,AL应该先显示2Ah(星号*的 ASCII 码),再显示62h(小写字母b)。如果某轮循环里AL的值跳过了预期字符,说明指针移动逻辑有误。

EMU8086 的另一个优势是能看到内存区的实时值变化。把变量窗口调到DS:0000附近,可以直接观察到pos单元在匹配成功后从0000h变为实际位置值。我在调试匹配成功用例时,str是*bb*bbu*bbup*bbbupt*bbupt,substr是bbupt,预期匹配位置是从str首地址偏移 14 个字节处(遇到第一个bbupt的位置)。

4.2 DOS 环境下用 Turbo Debugger 验证

如果只用 EMU8086 跑一遍就交差,很可能在真实 DOS 环境下遇到意想不到的问题。通常的做法是切换到 Turbo Debugger(TD)再验证一次,TD 的存储器转储视图能更直观地查看00h结尾的字符串布局。

DOS 环境的验证重点是两个:数据段偏移是否计算正确,以及INT 21H输出是否正常。EMU8086 对段寄存器的初始化和 DOS 实模式略有差异,直接在代码里加上mov ax, data和mov ds, ax这组初始化,能保证两种环境下数据访问一致。运行结果中匹配成功时显示Match found!,匹配失败时显示Not found.,我在两种环境下跑出的结果完全一致,说明程序兼容性没问题。

4.3 匹配成功与失败的边界用例

测试匹配成功用例时,我在主串里故意放入了多个可能的子串片段,确保程序找到的是第一次出现的位置而不是最后一次。原题目里str是*bb*bbu*bbup*bbbupt*bbupt,substr是bbupt,这个用例的优点在于有多处“几乎匹配”但实际不匹配的干扰位,比如*bbu和*bbup。这些干扰位能检验失败回溯是否正确——如果回溯逻辑出错,程序很可能会漏掉真正的匹配位置,或者在伪造的位置上报成功。

匹配失败用例我用的是str为*bb*bbu*bbup*,substr仍然是bbupt。这个用例末尾的三段式结构很考验程序的结束判断:str走到00h结束符时,substr还有upt三个字符没比较完,程序必须正确判定失败并将found置为00h。如果没有检查str的结束符就继续循环,会发生越界读取,读到数据段里后续的垃圾数据。

除了原题给出的用例,我还建议加测一组子串和主串完全相同的用例,这是检验pos是否为 0 的最好办法。如果pos输出不是 0,说明位置计算里减首地址的操作有偏差。

5. 避坑指南:字符匹配程序最容易翻车的五个细节

5.1 子串结束判断遗漏导致越界比较

现象:匹配成功时结果正确,但匹配失败时程序跑飞或输出乱码。原因:相等处理分支里只判断了str是否结束,漏了substr也可能先结束。解决:在str指针到达00h时,额外检查[di]是否也是00h,两者同时结束才能判定成功。这个检查在check_sub_end标签处补充。

5.2 失败后指针未回退导致每轮少比一位

现象:测试用例明明存在匹配,但程序就是不报告成功,或者报告的位置比实际靠后。原因:失败后直接inc si继续,没有把SI映射回首地址加失败次数。解决:inc cx后必须mov si, offset str加add si, cx,重新计算起点。这个问题的隐蔽性在于“少比一位”在短的测试数据里不一定立刻暴露,数据一长就翻车。

5.3 忽略 00h 结束符的比较

现象:程序报告匹配成功,但成功位置比预期靠后一个或两个字符。原因:compare_loop里取出的字符如果是00h,直接走相等或不等分支都会出错,必须单独处理。解决:cmp al, 00h后先判断是不是结束符,是则跳转到子串结束检查。这个分支是整个程序的防守底线。

5.4 段寄存器初始化遗漏

现象:EMU8086 里正常,换到 DOS 里结果全乱。原因:EMU8086 会自动设置数据段地址,但真实 DOS 环境不会。解决:程序开头加上mov ax, data、mov ds, ax两条固定写法,不管模拟器是否自动处理,显式初始化都不会有副作用。

5.5 匹配位置计算用错参考点

现象:匹配成功但pos数值比预期大或小固定差值。原因:pos必须以str首地址为 0,如果直接用SI的当前值存储,得到的是绝对地址而不是偏移量。解决:sub ax, offset str这步必须存在。还有个小坑是offset str返回的是编译期确定的偏移,如果str前面还有其他变量定义,这个偏移可能不是 0,更不能想当然地把结果当作从 0 开始的数字。

6. 验收与答辩:怎么证明程序真的正确

验收这个实验,不能只是跑通一个测试用例就结束。正确的验收流程要覆盖三类场景:匹配成功且位置正确、匹配失败且标志正确、边界情况(子串出现在开头、子串就是空串、主串和子串完全相等)。我建议准备一张测试记录表,逐项填写:

测试编号主串内容子串内容预期 found预期 pos实际结果
1*bb*bbu*bbup*bbbupt*bbuptbbuptffh14一致
2*bb*bbu*bbup*bbupt00h不定义一致
3bbuptbbbbuptffh0一致
4abc``(空串)ffh0一致

答辩时老师一般会追问三个问题:为什么禁止串操作指令、失败回溯的指针是怎么恢复的、pos的位置计算为什么用减法。回答思路分别是:考察手工控制指针的能力、指针恢复依赖CX累计失败次数重新映射、因为BX存放的是绝对地址而pos要求相对偏移。

还要能现场修改代码验证某个边界条件。比如把substr改成只在主串末尾出现的数据,演示程序能找到并报出正确位置;或者把主串改成一个字符然后重复几十遍,演示程序不会陷入死循环。能即兴改代码并跑通的答辩,比背 PPT 的效果好得多。

一个有用的习惯是每次改完测试数据,都重新走一遍单步调试的头三轮循环,确认指针初值和第一轮比较逻辑没被破坏。从那以后我每次做汇编实验,都会先把指针初始化和结束符判断从头到尾写完、看一遍,确认无误再跑完整程序,这样调试时间能省下一大半。希望这篇方案能帮你把实验流程完整走通,少踩几次字符指针的坑。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询