微机原理循环程序设计:LOOPZ与REPZ CMPSB指令对比与实验解析

发布时间:2026/10/23 9:34:49
微机原理循环程序设计:LOOPZ与REPZ CMPSB指令对比与实验解析
简介这是一份面向汇编语言循环程序设计学习的实验报告资源适用于微机原理与接口技术课程的本科生及初学汇编的读者。文档完整包含验证题与设计题两部分验证题要求判断BUF1与BUF2两个等长数据区内容是否相同给出分别采用LOOPZ计数循环指令和REPZ CMPS串操作指令的两种完整源程序并对两种实现中SI、DI指针初值设置的差异与优劣作了分析设计题要求统计BUF字数据区第一个零数据之后各数据中正数和负数的个数在搜索零数据时分别使用重复控制指令与串操作指令。压缩包内为单一doc文档大小224KB附有实验目的、实验内容、实验步骤、源程序清单、调试分析与指导教师评语等栏目便于读者直接参照实验报告格式完成作业或复习循环结构设计要点。目前已有2187人浏览学习对需要掌握重复控制指令、串操作指令及循环结构选用的学习者有直接参考价值。1. 循环程序设计从一次验证题看清两种循环写法的本质差异A同学第一次拿到这份「微机原理实验三循环程序设计」时最直观的反应是验证题给了两个源程序都是比较两个数据区是否相同看起来结果一样那为什么不直接给一个真正动手跑完两个程序、逐个寄存器盯过去之后才发现这两个程序走的根本不是同一条路——一个用 LOOPZ 重复控制指令靠计数器硬循环一个用 REPZ CMPSB 串操作指令让 CPU 自动比较。这道题想训练的不是「会不会写循环」而是「在什么场合选什么循环指令」。如果你的课设或毕设也卡在循环结构设计、重复前缀选用这类问题上这份实验方案正好可以作为完整参考它覆盖了循环程序设计的典型验证题和设计题从指令选型、循环结构设计到数据区统计每一层都有可抄作业的代码和可对照的调试思路。2. 实验背景与指令选型为什么同一道题要写两个程序2.1 实验目的拆解循环程序设计的三个考察点实验要求看起来只有两条实际上拆开是三个递进的考察点。第一是理解重复控制指令、串操作指令及重复前缀的格式与功能。重复控制指令指 LOOP、LOOPZ、LOOPE、LOOPNZ、LOOPNE 这一族它们靠 CX 寄存器计数决定是否继续循环串操作指令指 MOVS、CMPS、SCAS、LODS、STOS配合 REP 系列前缀可以一次性处理一整段内存数据。第二是练习建立合适的循环结构区分计数控制循环和条件控制循环。计数控制适合「循环次数事先知道」的场景比如遍历一个有固定长度的数组条件控制适合「循环次数取决于某个中间条件」的场景比如一直比较直到遇到不相等的字节。第三是同一个问题既可以用重复控制指令又可以用串操作指令时要能比较两者的优劣。这一条往往被忽略但恰恰是答辩时最容易被问到的点。2.2 两种实现路径的初步对比LOOPZ 循环 vs REPZ CMPSB 串操作验证题要解决的核心问题是判断 BUF1 和 BUF2 两个等长度的数据区中数据是否相同相同则 FLAG 单元置 0否则置 -1。数据区长度是多少看源程序里的定义DSEG SEGMENT BUF1 DB 21H, 3AH, 36 BUF2 DB 31H, 0C7H, 36 COUNT EQU $-BUF2 FLAG DB 0 DSEG ENDSCOUNT 的值为 $-BUF2也就是当前地址减去 BUF2 的起始地址。由于 BUF2 定义了 3 个字节COUNT 等于 3。注意这个等号是在汇编阶段计算的不是运行时计算的所以在程序里 MOV CX, COUNT 之后CX 里存的就是立即数 3。这就是典型的计数控制循环循环次数已知用 CX 做计数器。例 1 的程序用 LOOPZ 实现逐字节比较START: MOV AX, DSEG MOV DS, AX MOV SI, OFFSET BUF1 - 1 MOV DI, OFFSET BUF2 - 1 MOV CX, COUNT NEXT: INC SI INC DI MOV AL, [SI] CMP AL, [DI] LOOPZ NEXT JZ OK MOV FLAG, -1 OK: MOV AH, 4CH INT 21H这段代码的关键在 SI 和 DI 的初值。SI 指向 BUF1 - 1DI 指向 BUF2 - 1然后在循环体开头 INC 再访问。为什么要这样处理因为 LOOPZ 的语义是「CX 减 1若 CX 不为 0 且 ZF1 则跳转」跳转之前最后执行的 CMP 指令已经改变了 ZF 标志位。如果 SI 直接指向起始地址那么第一次循环就 INC 到第二个字节第一个字节永远比较不到。这里让指针初值后退一个单元就是为了配合「先 INC 再比较」的节奏确保从第一个字节开始比较。这种写法在手工汇编里很常见代价是代码可读性稍差但能保证循环体结构统一。例 2 的程序则换了一条路START: MOV AX, DSEG MOV DS, AX MOV ES, AX MOV SI, OFFSET BUF1 MOV DI, OFFSET BUF2 MOV CX, COUNT CLD REPZ CMPSB JZ OK MOV FLAG, -1 OK: MOV AH, 4CH INT 21H这里有三点设计上的差异。第一程序把 ES 段寄存器也设置成 DSEG 的段地址因为 CMPSB 指令规定源指针用 DS:SI目标指针必须用 ES:DI。第二CLD 指令将方向标志 DF 清零使 SI、DI 在每次比较后自动加 1。第三REPZ CMPSB 的执行过程是比较 DS:SI 和 ES:DI 指向的字节然后自动调整 SI 和 DICX 减 1若 CX 不为 0 且 ZF1 则继续比较。这段程序没有显式写 INC 指令指针的移动完全由硬件完成。提示REPZ 和 REPE 是同一个前缀的两种写法都表示「相等则重复」。与之对应的是 REPNZ / REPNE表示「不相等则重复」。这里字节数据用 CMPSB字数据则应该用 CMPSW。2.3 验证题两个程序的分析结论例 1 用了三条指令完成「指针移动 比较 判断」三步操作INC SI、INC DI、CMP AL, [DI]然后交给 LOOPZ 做循环控制。例 2 用一条 REPZ CMPSB 同时完成了比较、移动指针、计数减一、条件判断四件事。从代码量来看例 2 明显更简洁而且不需要 AL 寄存器作为中转因为 CMPSB 直接在内存单元之间比较。从灵活性来看例 1 的每个步骤都是显式控制的如果在比较之前需要对数据做加工比如只比较低 4 位或者先做某种变换例 1 的结构更容易插入额外操作。例 2 适合纯粹的逐字节相等比较不适合中间插入复杂逻辑。这两个程序的对比可以整理成一张表方便写实验报告或答辩时直接引用对比项例 1LOOPZ 循环例 2REPZ CMPSBSI 初值OFFSET BUF1 - 1OFFSET BUF1DI 初值OFFSET BUF2 - 1OFFSET BUF2指针移动方式循环体内 INC 显式移动硬件自动调整方向由 DF 控制数据中转经 AL 寄存器中转内存单元直接比较代码量循环体 5 条指令单条 REPZ CMPSB适用场景需要在比较前后插入额外处理纯粹的等长数据区相等性比较关于 SI、DI 初值区别的分析实验报告里通常要写清楚例 1 中 SI、DI 指向数据区首部的前一个单元因为循环体是先 INC 后比较例 2 中 SI、DI 直接指向数据区首部因为 REPZ CMPSB 的硬件逻辑就是「先比较后调整」。如果例 2 也把初值减 1第一次比较就会跳过第一个字节整个判断全部错位。这个知识点几乎是必考的答辩时导师多半会追问。3. 设计题实现统计第一个零数据后的正负数个数3.1 题目要求与循环结构设计思路设计题的要求分三个阶段第一在 BUF 字数据区中搜索第一个零数据第二从零数据后的下一个元素开始统计正数和负数的个数第三将统计结果分别送入 POSI 和 NEGA 存储单元。题目还特别要求「搜索第一个零数据时分别采用重复控制指令和串操作指令」意味着至少要用两种不同方法实现搜索阶段。原实验报告给出的源程序如下data segment buf db 23h,04h,11h,00h,0a3h,15h,6bh,0a7h,0c8h num equ $-buf1 posi db 0 nega db 0 data ends code segment assume cs:code, ds:data beg: mov ax, data mov ds, ax mov si, offset buf mov cl, num xor bl, bl xor al, al loop: dec cl cmp byte ptr [si], 0 jz next1 inc si jmp loop next1: inc si dec cl cmp cl, 0 jz exit cmp byte ptr [si], 0 jl next2 jz next1 inc bl mov posi, bl jmp next1 next2: inc al mov nega, al jmp next1 exit: mov ah, 4ch int 21h code ends end beg这段程序的数据区定义有 9 个字节从 23H 到 0C8H其中 00H 出现在第 4 个位置。NUM 等于 $-BUF1注意这里加 1 是为了把结束符也计入长度但实际遍历时并不访问结束符。程序把搜索阶段和统计阶段合并在一个循环里靠 JZ NEXT1 在遇到零时切换到统计逻辑。3.2 搜索阶段的目标分析与代码实现搜索阶段用的是一个条件控制循环循环继续的条件是「当前字节不为零」。这里我没有用 LOOP 指令控制循环次数因为搜索的结束条件不是「数够 N 次」而是「遇到零就停」。如果强行用计数循环就必须在循环体里先判断是否为零再决定是否跳出代码反而多一层嵌套。实际分析这段程序的执行流程CL 先初始化为 NUM每次循环先 DEC CL 再判断当前字节。找到零后跳到 NEXT1此时 SI 已经指向零数据本身所以 NEXT1 里第一条指令是 INC SI让 SI 越过零指向后面的数据。接下来进入统计阶段这是另一个条件控制循环循环条件是 CX 不为 0 且未到达数据区末尾。统计阶段对每个字节的判断逻辑是先和零比较如果小于零则跳 NEXT2 计负数如果等于零则说明后面还有零数据继续跳 NEXT1 进入下一轮判断如果大于零则不跳转直接 INC BL 计正数。这里有一个细节值得注意字节数据用 CMP BYTE PTR [SI], 0 判断符号是基于有符号数比较JL 指令在 SF≠OF 时跳转对于字节数据来说最高位为 1 的值如 0A3H、0A7H、0C8H都会被视为负数。如果这里用了 JC 或 JS处理方式就完全不同了这一点答辩时经常被追问。3.3 两个设计缺陷的修正方案原程序在两个地方值得商榷我一般在指导学生时会建议修正。第一BL 和 AL 作为正负数计数器在循环体内边计数边把结果存入 POSI 和 NEGA这会导致每次发现正数都重写一次 POSI。虽然最终结果正确但逻辑上不够清晰应该是统计完成后统一写入存储单元。第二搜索阶段和统计阶段挤在一个循环里遇到「零后面还有零」的情况时虽然 JZ NEXT1 能正确跳转但对阅读程序的人来说很容易误以为进入了死循环。稳妥的做法是分成两个明确的阶段。搜索阶段单独写可以这样实现mov si, offset buf mov cx, num search_loop: cmp byte ptr [si], 0 jz found_zero inc si loop search_loop found_zero: inc si这样搜索阶段结束后SI 一定指向零数据后的第一个字节CX 是剩余字节数。统计阶段在 CX 已知的情况下改用计数控制循环每处理一个字节就 LOOP 一次结构更清晰也不会出现原程序那样「搜索靠条件判断、统计又依赖 CX 剩余值」的混合状态。3.4 串操作指令实现搜索的另一种写法题目要求分别采用重复控制指令和串操作指令所以搜索阶段还可以用 SCASB 配合 REPNZ 前缀实现mov al, 0 mov di, offset buf mov cx, num cld repnz scasb ; 此时 DI 指向零数据后的下一个单元SCASB 指令用 AL 中的值与 ES:DI 指向的字节比较REPNZ 表示「不相等则继续扫描」一旦找到零便停止。执行结束时DI 已经自动指向零后的下一个字节省去了手动 INC 的步骤。这条指令省掉了循环体里的 CMP 和 INC是典型的硬件级循环优化。用这种方式实现搜索阶段整个程序可以压缩到十几条指令。但要注意 SCASB 要求 ES 指向数据段。如果数据段和附加段相同需要像验证题例 2 那样把 ES 也设置为 DSEG如果不同则要单独设置 ES。很多同学在这里翻车程序跑出来的结果完全不对但不知道原因通常就是 ES 没设置或者方向标志 DF 是置位状态导致 DI 往回走。4. 常见问题排查与避坑循环程序设计最容易翻车的五个点这一章整理的是我在类似实验和课设中反复遇到的踩坑记录。循环程序本身指令不多但因为涉及段寄存器、指针方向、计数器初值等细节任何一个地方疏忽都会导致结果莫名其妙。4.1 SI 和 DI 初值错位导致第一个字节被跳过现象程序能正常结束但 FLAG 的结果判断错误BUF1 和 BUF2 明明相同却判为不同或者明明不同却判为相同。用调试器单步执行时会发现第一个字节根本没被比较。原因例 1 里 SI、DI 初值是 OFFSET BUF1-1 和 OFFSET BUF2-1配合循环体开头的 INC 使用例 2 里是直接用 OFFSET BUF1 和 OFFSET BUF2配合 REPZ CMPSB 使用。如果把两个程序的处理方式混在一起比如给循环体带 INC 的程序把初值设成不偏移的版本或者给 REPZ CMPSB 的程序把初值减 1都会错位。解决先确认自己用的是哪种循环结构再决定初值要不要偏移。我的习惯是画一张内存图把 SI、DI 初始位置和每次循环后的位置标出来确认第一个字节一定能被访问到。4.2 CX 计数器初值错误导致循环次数不准现象数据区有 3 个字节但程序只循环了 2 次或者多循环了 1 次访问到数据区之外的垃圾数据。原因COUNT EQU $-BUF2 是在 BUF2 定义之后计算的正确值是 3。如果把这个 EQU 放在 BUF2 定义之前$ 的取值就不同了。另外有的同学会把 COUN T 计算成 $-BUF1由于 BUF1 和 BUF2 之间还有字节这个值就不是 3 了。设计题里 NUM EQU $-BUF1 也是同样道理。解决使用 EQU 计算长度时确保等号位置在所有数据定义之后。计算长度后可以先用调试器查看 CX 寄存器的值确认是否为预期数字。4.3 JZ 和 LOOPZ 的配合逻辑混淆现象验证题例 1 中程序在遇到第一个不相等的字节后正确地跳出了循环但最终 FLAG 的值总是不对。原因LOOPZ NEXT 这条指令本身有两个功能——CX 减 1 和条件跳转。跳出循环有两种可能一种是数据比较完了 CX 变为 0另一种是遇到了不相等字节 ZF0。跳出之后用 JZ OK 判断是哪种情况若 ZF1 说明是因为 CX 变 0 而跳出所有字节都相等若 ZF0 说明是遇到不相等字节而跳出。有的同学在 LOOPZ 之后没有用 JZ 判断而是无条件继续执行 MOV FLAG, -1结果两个数据区完全相同时也被判为不同。解决跳出循环后必须先判断 ZF再决定 FLAG 的值。4.4 方向和段寄存器的隐性问题现象用了 REPZ CMPSB 或 REPNZ SCASB 之后程序有时正常有时结果错误尤其是在同一个程序里多次使用串操作指令时。原因串操作指令的地址调整受 DF 标志控制默认状态下 DF0 是地址递增。如果在程序某处执行了 STD 指令将 DF 置 1后续串操作就从数据区末尾往前扫描结果自然错误。更隐蔽的是 ES 段寄存器问题CMPSB 固定使用 ES:DI如果 ES 没有设置正确比较的数据就来自错误的段。我第一次写这类程序时ES 忘设置数据区恰好位于 DS 段中程序就把 DS:SI 的内容和 DS:DI 的内容比较结果完全随机。解决每次使用串操作指令前强制执行 CLD并在程序开头确认 ES 已设置为目标数据段的段地址。4.5 数据定义的类型不匹配导致统计结果混乱现象设计题统计正负数个数BUF 定义为 DB字节有的同学会因为「字数据区」这个描述把类型改成 DW然后用 CMP WORD PTR [SI], 0 判断结果 16 位数的符号判断逻辑完全变化。原因「字数据区」在这个题目语境里可能指「数据区里的数据以字为单位划分」但实验数据的定义是字节。如果改成 DW0 的判断、正负的判断都要跟着调整而且用 SI 每步移动两个字节统计结果就完全不对了。解决动手前先确认数据区定义是 DB 还是 DW按照定义选择对应的 BYTE PTR 或 WORD PTR 限定符。5. 验收与答辩如何自查实验报告并应对提问5.1 功能验收标准验收循环程序设计实验需要从三个层面确认程序正确。第一个层面是结果正确性验证题中BUF1 和 BUF2 完全相同时 FLAG 必须为 0只要有一个字节不同 FLAG 就必须为 -1。我一般建议准备两组测试数据一组完全相同一组故意改掉中间某个字节。设计题中先手动算出给定数据的答案再和程序结果对照。BUF 定义中 23H、04H、11H 是正数00H 是第一个零之后 0A3H、0A7H、0C8H 是负数15H、6BH 是正数所以 POSI 应为 2NEGA 应为 3。第二个层面是循环逻辑正确性单步执行到找到零的那个位置确认 SI 确实指向零数据本身然后 INC 之后指向零后的第一个字节。第三个层面是边界条件如果数据区第一个字节就是零程序应该直接进入统计阶段如果整个数据区都没有零程序应该在循环结束后有处理逻辑不能在搜索阶段越界。5.2 答辩提问的四个准备角度答辩时导师通常会围绕四个角度提问。第一个角度是指令语义比如 LOOPZ 和 LOOP 的区别是什么REPZ CMPSB 和 REP CMPSB 的区别是什么。回答的要点是LOOP 只检查 CX 是否为 0LOOPZ 还要额外检查 ZF 是否为 1REP 不带条件地重复直到 CX 为 0REPZ 在 ZF1 时才继续。第二个角度是指针问题比如为什么例 1 的 SI、DI 初值要减 1为什么例 2 不需要。回答的关键是「先 INC 后比较」和「先比较后自动调整」的差别。第三个角度是正负数的判断逻辑比如用 JL 判断负数时是基于有符号比较如果数据中出现了 80H 到 FFH 之间的值要能解释清楚为什么这些值被视为负数。第四个角度是循环结构的选择理由比如为什么搜索阶段用条件控制循环而统计阶段用计数控制循环回答时要说清楚两者的适用场景。5.3 实验报告撰写的检查清单写实验报告时我通常按下面的清单检查一遍源程序是否完整可汇编段定义、ASSUME、END START 是否齐全关键指令是否有注释尤其是 LOOPZ、REPZ CMPSB、CLD 这些影响程序流程的指令有没有分析两个验证程序的异同这是实验指导老师最看重的分析部分测试结果是否包含边界数据比如两个数据区完全相同的情况心得部分是否结合了具体的踩坑经历而不是泛泛地说「学会了循环」。如果程序调试了很久才跑通把当时的错误原因写进心得里反而是报告中最有说服力的部分。5.4 调试技巧收尾最后分享一个我的个人习惯写完循环程序后不要急着用 RUN 直接运行全速跑结果而是强制自己单步执行一遍完整的循环流程。循环次数少就全走完循环次数多就走三遍——第一遍、中间一遍、最后一遍。第一遍观察指针初值是否正确进入循环体中间一遍观察计数器和标志位的变化规律最后一遍观察退出条件是否按要求触发。尤其是 LOOPZ 这种带有两个退出条件的指令单步走到退出时多看一眼 ZF 标志是从哪里来的是最后一次比较产生的还是之前比较残留的。从那以后我每次做循环相关的程序设计实验都会强制走一遍「三遍观察法」这几乎能拦住 90% 的指针错位和标志位误判问题希望你也被这份方案帮到。本文还有配套的精品资源点击获取