资讯动态

8086汇编字符串匹配实验:禁用串指令后的双指针实现与调试

发布时间:2026/10/22 1:10:21 来源:尧图企业网站定制
简介这是一份面向微机原理与接口技术课程学习者、尤其需要完成8086汇编语言字符串匹配实验的学生的完整实验报告。报告以某一任意长度字符串str和子串substr均以00h结束为对象详细说明如何在不使用串操作指令的前提下用8086汇编实现子串搜索并将found单元和pos单元的置位逻辑、算法流程图、完整代码、EMU86与DOS双环境调试结果一并整理适合用于上机参考、算法对照与报告撰写模板。压缩包仅含1个docx文档整体大小839KB文件结构集中核心内容围绕实验要求、算法思路、代码与注释、调试分析及心得体会展开。这份材料已有1700余人学习下载。通过阅读与分析读者可掌握借助变址寄存器si/di、cx计数与条件跳转搭建匹配循环的方法理解字符比较、相等/不相等分支处理以及匹配位置定位原理同时能借鉴作者对成功与失败案例的调试思路提升汇编程序设计与排错能力。1. 一个 8086 汇编语言字符串匹配实验禁掉串指令后搜索逻辑反而更清楚某个晚上A同学对着 EMU8086 发愁题目要求在一串以 00h 结束的字符串里搜索子串找到就把 found 置为 ffh、把位置写进 pos还明令禁止使用串操作指令。他卡了一晚上因为不能偷懒用 cmpsb 一句搞定每个字节都得自己取、自己比、自己回退。这正是 8086 汇编语言字符串匹配实验的价值——用最小指令集把搜索算法完整搭出来。这份实验报告把题目要求、算法框图、带注释代码、EMU8086 和 DOS 双环境调试结果完整保留了下来。适合正在学微机原理、被课程实验卡住或者想补汇编基本功的开发者。2. 题目约束与算法设计00h 结束标志和 pos 参考点才是隐藏难点这一章先想清楚再动代码。题目看起来只有一句话实际给了三个互相咬死的约束任何一个理解偏了程序都会在调试阶段反复横跳。2.1 三个硬约束长度、结束标志与禁止串指令第一条两个串都以 00h 结束str 长度小于 200hsubstr 长度小于 80h。200h 是 512 字节80h 是 128 字节如果按十进制理解就会把存储上限估小。这意味着程序不需要也不应该先数一遍字符串长度结束全靠每轮比较时读到 00h 来判断。第二条约束在看结果found 是单字节标志找到置 ffh、找不到置 00hpos 存放匹配位置以 str 首地址为 0 参考点。意思是 str 的第一个字符位置是 0不是 1所以匹配到 str 的第 14 个字节时pos 里存的是 130Dh。这个基准一旦错后面验证全对不上。第三条禁止串操作指令把 movsb、cmpsb、scasb、stosb、lodsb 全部排除。串操作指令一条能做“取字符、指针自增、比较”三件事禁掉之后所有动作都得拆成 mov / cmp / inc / jmp 的显式组合。这三条合起来把实现逼成了一个朴素的双指针循环外层指针逐个扫主串的可能起点内层指针从子串头开始逐字符比较内层失败则外层起点加一重来。理解了这一点程序结构就基本定型了。2.2 手工推演先纸上跑通再写汇编这道题在报告里特别强调了“设计时通过举例分析确定循环体与跳转条件”我建议照做。就拿成功用例来推。strbbbbubbupbbbupt*bbuptsubstrbbupt。令匹配起点为 str[0]str[0]*substr[0]b不等起点移到 1str[1]b 等于 substr[0]str[2]b 等于 substr[1]str[3]* 不等于 substr[2]b起点移到 2起点 2 同样在第三个字符处失败起点 3 从 * 开始直接失败一直推到起点 13str[13..17] 正好是 bbupt此时子串指针读到 substr[5]00h匹配结束pos13把这几轮手算走完两个关键结论自然浮出来。第一失败后的回退动作必须是“外层起点 1”不能因为内层已经比了 4 个字符就把指针往回跳 4 位——汇编里没有 for 循环的自动复位跳多了会漏掉错位匹配比如主串开头的bb要到第二个 b 才开始正确匹配的情况。第二匹配成功的判定时机在“子串指针读到 00h”而不是“当前字符相等”。str[1]b 也等于 substr[0]b但那时子串还没走完只有把 bbupt 五个有效字符全部比过、下一轮读到 0 时才能置 found。2.3 数据段布局与寄存器分工先把“谁存什么”钉死汇编程序里最折磨人的不是指令不会写而是寄存器职责反复横跳。报告代码的数据区建议照这个结构定义str db *bb*bbu*bbup*bbbupt*bbupt, 0 substr db bbupt, 0 found db 00h pos dw 0000hstr 和 substr 用逗号分隔的字符初始化末尾的 0 就是 00h 结束标志found 因为只存 ffh/00h定义成 db 就够pos 必须定义成 dw原因在第 3 章讲 pos 计算时会看到。寄存器分工直接决定代码可读性这套思路沿用报告| 寄存器 | 职责 | | SI | 主串当前比较指针每轮从匹配起点开始 | | DI | 子串当前比较指针每轮从 substr 头开始 | | BX | 外层匹配起点匹配成功时它就是 pos 的基准 | | AL / DL | 主串 / 子串当前待比较字符 | | AX | pos 计算时的临时寄存器 |为什么用 BX 保存匹配起点而不是 CX因为成功时要用“BX - offset str”算位置BX 可以参与这种偏移量运算CX 更适合做失败次数或比较位数计数。为什么内层指针选 SI/DI 不选 BX/BP一是 SI/DI 作为变址寄存器与偏移连用的语义更清晰二是把 BX 从内层循环里解放出来保住匹配起点不被推进。这份分工表最好贴在屏幕边再写循环体不然写一半就会忘记 SI 到底指向哪儿了。3. 核心模块拆解内层字符比较与失败回溯的完整链路报告里的代码与注释是逐段组织的为了讲清执行链路我按“初始化 - 内层比较 - 不相等处理 - 结果写入”把核心循环恢复成下面四段。寄存器分工沿用上一章的表看代码时对照着读。3.1 初始化段装载与双指针预置ASSUME cs:code, ds:data start: mov ax, data mov ds, ax mov bx, offset str ; bx 外层匹配起点 mov si, bx ; 内层主串指针从起点开始 mov di, offset substr ; 子串指针指向子串头 mov cx, 0000h前两条是任何完整汇编程序都要做的段寄存器初始化把数据段装载进来。后面三行把三个核心量一次性设好bx 指向 str 首地址它是“当前正在尝试的匹配起点”si 复制 bx保证第一轮内层比较从 str[0] 开始di 指向 substr。cx 清零备用报告里提到的“失败计数值加一”可以用它记录某个起点失败前已经比对的字符数。提示si 和 di 的初始赋值顺序不要颠倒。先有 bx 再有 si程序逻辑上就锁死了“si 永远从 bx 出发”这个约定后面的回退代码才不容易写错。3.2 内层比较先判子串结束再判主串结束这是整个程序的心脏。报告里说的“字符比较、相等处理、不相等处理”都收在这个循环里outer_loop: inner_loop: mov dl, [di] ; 取出子串当前字符 cmp dl, 00h je match_found ; 子串读到结束符 有效字符全部匹配完 mov al, [si] ; 取出主串当前字符 cmp al, 00h je match_fail ; 主串先结束 当前起点必然失败 cmp al, dl jne mismatch ; 两字符不等处理失败回退 inc si ; 相等则双方指针后移继续比下一位 inc di jmp inner_loop比较顺序是这份代码最关键的细节先判断子串是否读到 00h再判断主串是否读到 00h最后才比较字符是否相等。如果反过来先做 al 与 dl 的比较再在“相等”分支里查 dl 是否为 0那么当 substr 为空串、或者主串与子串同时到结尾时程序会在结束判断之前先执行一次多余的取指与比较指针已经越过边界。先判子串结束等于把“匹配成功”的判定提前到了最安全的位置。参数作用dl 是子串当前字符al 是主串当前字符00h 在这里既是数据边界也是循环哨兵je/jne 按上一轮 cmp 的结果跳转。这段循环不需要计数器两个串都靠 00h 自然终止。3.3 不相等分支bx 起点回退而不是 si 自增内层字符不等代表当前起点不成立要做的是整体换一个起点重新试mismatch: inc bx ; 匹配起点后移一位失败计数加一的效果 mov si, bx ; 主串指针拉回新的起点 mov di, offset substr ; 子串指针也回到子串头 jmp outer_loop这里最容易写错的是把“inc bx; mov si, bx”写成“inc si”或者“sub si, cx”。错误原因要去理解回退语义si 此刻已经跟着内层循环走了若干位如果直接在 si 基础上加一相当于从“当前比对失败位置的下一个字符”继续尝试中间那些错开的起点全被跳过而正确做法是把 si 重置回新的外层起点。报告里那句“失败计数值加一并映射到指针”落到代码上就是这一小段——失败一次起点就前进一格。这段代码还隐含一个效果即使一个字符都没比就发现主串结束al0 而 dl≠0也会走 mismatch 把起点加一。所以 match_fail 这个出口只会在外层把所有起点都扫完、主串指针完全走到 00h 时被触发不会提前退出。3.4 成功出口found 置 ffhpos 用 bx 减首地址算两个最终出口逻辑都极短但容易写差match_found: mov found, 0ffh mov ax, bx ; bx 仍是最初匹配的起点 sub ax, offset str ; 换算成以 str[0] 为 0 的偏移 mov pos, ax jmp exit match_fail: mov found, 00h exit: mov ah, 4ch ; 程序退出返回 DOS int 21hpos 必须用 bx 算这是最容易翻车的地方。内层匹配成功时si 已经推进到匹配段之后、甚至可能停在主串的 00h 上di 停在 substr 的 00h 上整个内层循环里只有 bx 没有动过它就是匹配在 str 中的真实起点。sub ax, offset str 把绝对地址差转成相对偏移——如果程序加载后 str 从段内偏移开始字符 b 相对 str 首地址偏移是 13减完得到 0Dh正好对应题目要求的“以 str 首地址为 0 参考点”。别忘了 pos 定义成 dw 的原因str 最长 200h偏移可以到 511一个字节存不下。最后 mov ah, 4ch / int 21h 是 DOS 的标准退出方式EMU8086 和真实 DOS 都认。4. 调试与验证两组成败用例在 EMU8086 与 DOS 下的核对报告调试部分的核心结论是EMU8086 环境下和 DOS 环境下运行结果一致且符合题目要求。这一章把为什么要跑这两个环境、核对哪些量讲清楚。4.1 用例设计用“半截匹配”前缀逼出失败回溯成功用例选择得非常刻意strbbbbubbupbbbupt*bbuptsubstrbbupt。这个主串前面藏了 bb、bbu、bbup 三个“半截匹配”每个都差最后一个字符。用来干嘛专门验证上一章 mismatch 分支程序必须在这三个半截匹配上反复失败、反复把起点加一最后才能停在 13 这个真正匹配的位置。如果代码把失败回退写成“si 加一继续”而不是“bx 加一再拉回 si”跑这个用例时会从某个错位起点一路冲下去结果要么死循环要么 pos 错乱。成功用例的预期 pos 可以手工算一遍主串按 0 起编号01b2b34b5b6u78b9b10u11p1213b。从 13 号开始的 bbupt 是第一个完整匹配13518第 18 个字符是 t再下一个是 19 号 *不是 00h所以成功判定发生在 di 读到 substr[5]0 的那一轮此时 si 停在主串 19 号之后的位置——这个数字正好用来验证“为什么不能用 si 计算 pos”。失败用例 strbbbbubbupsubstrbbupt主串把能匹配的前四个字符都给齐、最后一个 t 砍掉程序要把从各个起点启动的内层比较全部跑完最终在外层起点越过 str 有效字符后触发 match_fail。| 检查项 | 成功用例预期 | 失败用例预期 | | found | 0ffh | 00h | | pos | 0Dh十进制 13 | 保持原值题目未要求清零 | | 程序退出码 | 正常返回 DOS | 正常返回 DOS |4.2 双环境对照先单步看寄存器再全速运行查数据段拿到程序不要急着全速运行。先在 EMU8086 里用单步走第一遍重点观察 si、di、bx 三个指针的变化。以成功用例为例第一轮内层循环里 si 从 0 推进到 3 附近碰到 * 与 b 不等触发 mismatch 跳转后 bx 从 0 变为 1、si 被拉回 1这个“拉回”动作肉眼可见说明失败回溯逻辑在工作继续单步到内层走到 substr 末尾dl 变成 00h 触发 je match_found指针停在正确位置说明结束判定顺序没问题。按四步走完一遍完整链路编译源码为 EXE 后在数据段定义处下断点运行到主循环前单步跑完第一轮内层循环记录 si 从几变到几、触发的是 jne 还是 je全速运行到程序退出打开内存窗口看 found 和 pos 所在地址的内容换失败用例重复 1-3重点确认触发的是 match_fail 而不是 match_found。EMU8086 查看内存结果的方法是打开内存窗口滚动到数据段偏移处found 地址内容应为 FF成功用例或 00失败用例pos 是双字节且 x86 小端存储成功用例应看到 0D 00。如果这里显示 00 0D说明字节序搞反了。然后换 DOS 环境再验一遍把同样的源码汇编、链接成 EXE放进 DOSBox 之类的 DOS 环境运行。运行前先记录程序是否正常退出再用 debug 加载同一份 EXE。debug 下先执行 r 命令看各段寄存器找到 DS 的值用 u 反汇编主程序里的 offset str 指令确认 str 在数据段内的偏移然后根据“数据段基址 段内偏移”用 d 命令直接看 found 和 pos 两个内存单元。整个过程看起来繁琐核心目的只有一个验证程序不依赖任何绝对地址假设。EMU8086 加载方式和真实 DOS 不同DS 初值、PSP 头大小都不一样如果代码里有任何依赖固定段址的写法DOS 下必错。报告结论里“DOS 环境下调试与 EMU8086 结果一致”这句话替后来人扫掉了这个最常见的“仿真能跑、真机翻车”的坑。最后提醒一点不要在真机上直接全速跑第一次。先用单步确认三个指针的初值都指向预期位置bx 应指向 str 首字符di 应指向 substr 首字符al、dl 在第一次内层展开时应分别为 与 b。这些值对上了程序的基本路径才算立住。5. 避坑笔记单步全对、运行死循环这类现象怎么排查这章把调试过程中最常踩的四个坑集中写出来。每一条都是“现象 - 原因 - 解决”的结构前两条属于代码逻辑后两条属于运行环境。5.1 单步全对、直接运行死循环现象用单步逐条执行每一行跳转都对结果一切正常一旦点全速运行程序就卡死Run 键失去响应。原因这类问题多出在内层循环缺少出口保护。比如把内层循环写成“先比较字符、相等再判 dl 是否为 0”当 substr 以 00h 结束时程序还会再执行一次 mov dl,[di] 和 cmp al,dl此时 di 已经指向 00h 后面的内存读到什么全看运气如果读到非 0 字节循环会一直比较下去直到撞上非法地址。单步时你肉眼看到 dl0 就手动中断了自然发现不了这个问题。解决严格执行内层循环入口顺序先把“dl 等于 0 则跳转 match_found”放在最前面保证子串结束被无条件提前拦截。如果已经陷入死循环用 EMU8086 的暂停功能看 IP 指针反复在几条 cmp / jmp 之间转圈基本就是结束判定缺失IP 跑到数据段范围说明指针越界读到了垃圾值。改完后用失败用例全速运行 20 秒不死循环即为通过。5.2 pos 偏大 1用错了计算基准现象匹配成功found 已经是 FF内存里 pos 的值却比手算结果大 1或者大一个固定偏移。原因代码里写成了“mov ax, si; sub ax, offset str”。si 在匹配成功时已经多走了一步——内层比较是“先取字符、后 inc”最后一个有效字符比较完、触发成功判定的那一轮里si 还停留在匹配段的末尾之后而不是起点。多走的这一步正好让结果偏 1。解决pos 计算改用 bx。bx 在整个内层循环里不被修改是唯一一个“匹配起点”的精确快照。改完后用成功用例核对手算 str[13] 开始匹配程序输出的 pos 必须是 13不是 14 也不是 12。这个坑的排查成本很低先看 pos 偏差大小偏差为 1 时九成是 si 和 bx 用混了。5.3 主串正好以匹配段结束结束判定顺序导致漏判现象把成功用例的主串尾巴改成恰好让 bbupt 贴着字符串结尾比如 strxxbbupt程序反而报匹配失败found 被置成 00h。原因把“主串结束”和“子串结束”两个判定混在一起了。常见错误写法是先比较 al 与 dl相等后再看 dl 是否为 0当 substr 的 00h 与主串的 00h 相遇时cmp 结果相等程序理应走“匹配成功”分支但如果把主串 00h 的检查放在前面就会先跳 match_fail判定整体失败。两种写法在“主串与子串同时到结尾”的场景下结论完全相反。解决按“先 dl 判 0 成功再 al 判 0 失败”的顺序固定两个判定的优先级。子串读到 0 永远表示匹配成功主串读到 0 永远表示“当前起点失败”两者不互为等价条件。补充测试用例substr 只有 1 个字符、且就出现在主串末尾是最能暴露这类问题的边界。5.4 EMU8086 正常、DOS 下数据段内容异常现象同一个程序在 EMU8086 里跑完found/pos 全对拿到 DOS 环境跑完用 debug 查看数据段时发现 pos 的值像随机数found 也不对。原因两种环境对程序和数据的加载地址不同。EMU8086 的简化加载模型和真实 DOS 的 EXE 加载时DS 指向的段基址、程序段前缀占用的头部大小都不完全一样。如果代码里误用了绝对段地址比如手工写死 mov ax, 某个具体数值 再 mov ds, ax换个加载地址必然错。解决所有指针计算一律用 offset 伪指令加寄存器相对偏移不要写死任何绝对段址段初始化严格按 3.1 的“ASSUME mov ax, data mov ds, ax”顺序走。验证时在 debug 下先 r 看 DS 与 CS 的段值两者指向同一段说明加载成了 COM 模型pos 的绝对地址要按 PSP 基址重新算EXE 模型下 DS 才是数据段。成功用例和失败用例各在两个环境跑一遍每次都查内存单元而不是只看寄存器。6. 二次改造从过实验到多匹配定位与大小写无关实验跑通了这套双指针框架还能接着往下改。最简单的是把“只找第一个”改成“找全部匹配”match_found 分支里不再直接跳 exit而是把 pos 改成一个 dw 数组用当前 bx 写入数组并让数组下标加一然后执行“inc bx; mov si, bx; mov di, offset substr; jmp outer_loop”继续扫剩余主串。注意每次写入前要判断数组容量避免越界。这个改动几乎不碰内层比较循环只动出口分支是对“结果写入”这一段最好的练手。第二个改动是忽略大小写。在 cmp al, dl 之前对两个字符各做一次归一如果字符落在 A41h到 Z5Ah区间先执行 or al, 20h或 or dl, 20h把它转成小写再比较。0-9 数字和 00h 哨兵不受影响。注意 ascii 码里 20h 恰好是大小写字母的差值用 or 而不是 add保证只有大写字母被改动、其他字符原样保留。第三个改动是移植到 MASM/TASM。EMU8086 里可以直接写 mov ax, dataMASM 下通常要写成完整的段定义.model 小内存模式 .stack .data .codeoffset 要写成 OFFSET 形式注释风格和伪指令也要适配。这类移植翻车点集中在段名与 ASSUME 声明不一致建议用报告的代码对照着逐行替换不要凭记忆重打。验证改造后的程序边界用例比功能用例更重要substr 只有一个字符substr 与主串完全相等substr 出现在主串最后一位str 首字符就是 00h空主串。这四个用例能同时打到 5.3 的结束判定和 6.1 的数组边界跑完它们再跑正常用例基本可以放心收工。从那以后我每次写带指针循环的汇编程序都强制自己先把“寄存器分工表 内存单元表”画在纸上再写第一行指令写完先手工推三个用例再一次上机。这套流程帮我避开过好几轮单步到凌晨的尴尬希望帮到你。本文还有配套的精品资源点击获取

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

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

免费获取报价 →
↑