深入理解 x86 汇编中的重复前缀:REP、REPZ/REPE、REPNZ/REPNE(进阶详解版)

article/2025/6/6 1:52:55

一、重复前缀:串操作的 “循环加速器”

如果你写过汇编代码,一定遇到过需要重复处理大量数据的场景:

  • 复制 1000 字节的内存块
  • 比较两个长达 200 字符的字符串
  • 在缓冲区中搜索特定的特征值

手动用loop指令编写循环?代码冗长不说,效率还低 —— 因为 CPU 对rep前缀有专门的硬件优化。
x86 提供了 3 种重复前缀,它们是串操作指令的 “循环加速器”,能让数据批量处理变得又快又简单:

本文需要运用的知识(需要详细了解可点击对应的点):

  • Flags寄存器
  • 串操作指令

二、无条件循环王者:REP(Repeat)

2.1 核心功能与 “无脑循环” 逻辑

REP的英文名是 “Repeat”,顾名思义:无条件重复执行后续的串操作指令,直到计数器CX归零
它就像一个 “死循环”,只要CX不为 0,就一直干活,典型场景包括:

  • 内存复制MOVSB/MOVSW
  • 内存填充STOSB/STOSW
  • I/O 批量传输INSB/OUTSB

2.2 执行流程可视化

用伪代码表示rep movsb的执行过程:

初始化:CX=重复次数, DF=方向, SI=源地址, DI=目标地址  
循环开始:  while (CX > 0) {  复制DS:SI → ES:DI (1字节/字/双字)  SI += 步长(DF=0时+1/+2/+4,DF=1时-1/-2/-4)  DI += 步长  CX -= 1  }  
循环结束  

流程图:

2.3 必知必会的三个关键点

  1. 位数限制

    • 16 位模式:使用CX(16 位),最大重复 65535 次
    • 32 位模式:使用ECX(32 位),前缀变为REP(自动扩展)
    • 64 位模式:使用RCX(64 位),但传统 BIOS 引导程序常用 16 位模式
  2. 方向标志DF

    • 必须提前用cldDF=0,地址递增)或stdDF=1,地址递减)设置
    • 例:复制字符串时用cld,从低地址向高地址复制;若反向复制(如重叠内存块),用std
  3. 段寄存器配置

    • 源地址固定为DS:SI,目标地址固定为ES:DI
    • 若数据段和目标段相同(如在同一段内复制),需手动设置es = ds
      mov ax, ds  
      mov es, ax  ; 确保ES=DS  
      

2.4 实战:用REP MOVSB复制 100 字节

[org 0x7c00]  ; 引导扇区加载地址(16位实模式)  
start:  ; 1. 初始化段寄存器  mov ax, 0x07C0  mov ds, ax  ; 数据段=代码段(引导扇区默认)  mov es, ax  ; 目标段=数据段(同一段内复制)  ; 2. 设置参数  mov si, source_buffer  ; 源地址:0x7C00 + source_buffer偏移  mov di, dest_buffer    ; 目标地址  mov cx, 100            ; 复制100字节  cld                    ; 地址递增(SI/DI每次+1)  ; 3. 执行批量复制  rep movsb              ; 自动循环100次,每次复制1字节  jmp $                  ; 卡死在这里,方便观察结果  source_buffer:  db 0x55, 0xaa, 0x00    ; 示例数据(前3字节为引导标志)  times 97 db 0x00       ; 补满100字节  dest_buffer:  times 100 db 0x00      ; 目标缓冲区(初始化为0)  ; 引导扇区填充  times 510-($-$$) db 0  db 0x55, 0xaa          ; 有效引导扇区标志  

三、相等时循环:REPZ/REPE(双胞胎指令)(或 REPE,Repeat While Zero/Equal)(-->>标志位寄存器)

3.1 “找不同” 神器:只在相等时继续

REPZREPE是完全等价的指令(Z代表 Zero,E代表 Equal),功能是:
CX≠0且当前操作结果 “相等”(ZF=1)时,继续重复执行串操作指令
一旦出现 “不相等”(ZF=0)或CX=0,立即停止循环。
典型场景:字符串比较(找到第一个不相等的字符位置)。

3.2 标志位魔法:ZF 如何控制循环

REPE CMPSB为例(比较两个字节是否相等):

  1. 第一次执行CMPSB:比较DS:SIES:DI的字节
  2. 若相等(ZF=1)且CX>0SI/DI移动,CX-1,继续循环
  3. 若不等(ZF=0):立即终止循环,此时SI/DI停留在第一个不等的位置

3.3 循环结束后的 “状态密码”

  • ZF=1CX=0:所有比较的字节都相等(字符串完全相同)
  • ZF=0:找到了第一个不相等的字节,SI/DI指向该位置的下一个地址
  • 易错点:CX存储的是剩余次数,实际执行次数 = 初始CX - 最终CX

3.4 实战:用REPE CMPSB判断字符串是否全等

; 比较string1和string2是否完全相同(区分大小写)  
mov si, string1  
mov di, string2  
mov cx, 20          ; 假设最长20字符  
cld  
repe cmpsb          ; 相等时继续比较  jz full_match       ; ZF=1且CX=0 → 完全相同  
; 否则,ZF=0 → 找到第一个不等的位置  
; 此时SI指向string1的不等字符,DI指向string2的不等字符  full_match:  ; 执行字符串相等的逻辑  

四、不等时循环:REPNZ/REPNE(反逻辑搭档)(或 REPNE,Repeat While Not Zero/Not Equal)

4.1 “找相同” 专家:只在不等时继续

REPNZREPNE也是等价指令(NZ代表 Not Zero,NE代表 Not Equal),功能是:
CX≠0且当前操作结果 “不相等”(ZF=0)时,继续重复执行串操作指令
一旦出现 “相等”(ZF=1)或CX=0,立即停止循环。
典型场景:字符串搜索(找到第一个匹配的字符位置)。

4.2 与REPZ的灵魂对比表

特性REPZ/REPEREPNZ/REPNE
重复条件ZF=1 且 CX≠0ZF=0 且 CX≠0
目标寻找第一个不相等的位置寻找第一个相等的位置
适用场景字符串比较(找不同)字符串搜索(找相同)
等价关系完全等价(别名)完全等价(别名)

4.3 避坑指南:SCASB后的地址 “偏移”

当使用REPNZ SCASB搜索字符时:

  • 每次SCASB会先比较ALES:DI,再根据DF移动DI
  • 若找到匹配字符(ZF=1),DI已经指向下一个字符
  • 所以需要手动dec di,让DI回到匹配字符的地址:

    assembly

    repnz scasb  ; 未找到时循环  
    jz found_char  
    dec di       ; 修正DI到匹配字符的位置  
    

4.4 实战:用REPNZ SCASB定位第一个空格

mov di, buffer       ; 搜索缓冲区  
mov al, 0x20        ; 空格的ASCII码  
mov cx, 100         ; 最多搜索100字节  
cld  
repnz scasb         ; 未找到空格时继续  jz space_found      ; ZF=1 → 找到空格  
; 否则,CX=0 → 未找到  space_found:  dec di          ; DI回退到空格的地址  ; 此处DI即为空格的位置  

五、终极对比表:3 分钟吃透所有细节

前缀全称重复条件适用指令核心作用结束时 ZF=1 含义
REPRepeatCX≠0MOVS/STOS/INS/OUTS无条件批量操作无(不依赖 ZF)
REPZRepeat While ZeroCX≠0 且 ZF=1CMPS/SCAS相等时继续(找第一个不等)所有操作都相等(CX=0)
REPERepeat While Equal同上同上别名,功能完全相同同上
REPNZRepeat While Not ZeroCX≠0 且 ZF=0同上不等时继续(找第一个相等)找到相等项(CX 可能非零)
REPNERepeat While Not Equal同上同上别名,功能完全相同同上

六、常见问题与反套路技巧

Q1:为什么REPZREPE是两个名字?

  • 历史原因:早期 x86 架构中,REPE用于CMPS(比较相等时重复),REPZ用于SCAS(扫描零值时重复),后来统一为等价指令。

Q2:如何计算重复次数?

  • 字节操作(如MOVSB):CX=字节数
  • 字操作(如MOVSW):CX=字节数÷2
  • 例:复制 26 字节的字符串(字符 + 属性各 13 字节),movsw需要cx=13

Q3:忘记设置DF会怎样?

  • DF的初始值是 “随机的”(由上一条影响标志位的指令决定),可能导致SI/DI向反方向移动,数据越界或覆盖关键区域。

Q4:32 位模式下前缀会变化吗?

  • 指令格式不变,但计数器变为ECX,且MOVS等指令支持双字操作(MOVSD),步长变为 4 字节。

七、结语:从 “会用” 到 “精通” 的进阶之路

重复前缀是 x86 汇编中 “用对了省时省力,用错了 Debug 到崩溃” 的关键技术。掌握它们的核心:

  1. 牢记条件REP无脑循环,REPZ找不等,REPNZ找相等
  2. 检查三要素CX次数、DF方向、DS/ES段寄存器
  3. 善用标志位:循环结束后通过ZFCX判断结果

下次遇到批量数据处理时,试试用重复前缀代替手动loop—— 你会发现汇编代码可以既高效又优雅。

如果还有疑问,欢迎在评论区留言,我们一起拆解更多汇编 “黑魔法”!


http://www.hkcw.cn/article/ydxBKLSyXL.shtml

相关文章

【PCB设计】STM32开发板——原理图设计(电源部分)

一、PCB设计流程 二、准备工作 1.点击文件新建工程并命名 2.新建图页 在绘制较为复杂的原理图时,可以建立多个图页,使得原理图更加清晰。 右击原理图→新建图页 右击→重命名 3.设计规则相关配置 取消勾选第22个 4.调整页面大小 5.放置“电源树”图片…

C++仿RabbitMQ实现消息队列

前言 本项目将使用 C 在 Linux(CentOS 7.6) 环境下开发一个仿 RabbitMQ 的简易消息队列。 开发和调试环境如下: 操作系统:Linux (CentOS 7.6) 编辑器:Visual Studio Code / Vim 编译器:g(GNU…

离散数学_数理逻辑(二):命题逻辑的推理

前言 每一件事都存在现象和本质.现象是表面,本质是内在.数学可以说是自然科学之母,是一切自然现象的本质.对于编程,表面上是在写代码,实际上是在用离散数学理解问题和解决问题. 引入 命题逻辑的推理部分. "推理"在思考中占了很大比重.笔者曾经把学习方法分了两种:一…

KITTI数据集(计算机视觉和自动驾驶领域)

KITTI(Karlsruhe Institute of Technology and Toyota Technological Institute at Chicago)数据集是计算机视觉和自动驾驶领域中最广泛使用的基准数据集之一。它由德国卡尔斯鲁厄理工学院和美国芝加哥丰田技术研究所联合发布,旨在推动自动驾…

力扣4.寻找两个正序数组的中位数

文章目录 题目介绍题解 题目介绍 题解 题解链接:题解 核心思路:通过二分查找的确定分割点使左右两部分元素数量相等。 class Solution {public double findMedianSortedArrays(int[] nums1, int[] nums2) {int n1 nums1.length;int n2 nums2.length…

Windows下将Nginx设置注册安装为服务方法!

一、需求背景 每次启动 Nginx 都要去到 Nginx 安装目录下寻找 nginx.exe 文件点击,很是麻烦。 并且远程登录桌面,有时注销用户,会把在当前用户打开的nginx关闭了。 于是考虑可不可以跟其它服务一样能够开机自启?显然是可以的。…

web第九次课后作业--SpringBoot基于mybatis实现对数据库的操作

前言 在前面我们学习MySQL数据库时,都是利用图形化客户端工具(如:idea、datagrip),来操作数据库的。 在客户端工具中,编写增删改查的SQL语句,发给MySQL数据库管理系统,由数据库管理系统执行SQL语句并返回执…

SpringBoot+XXL-JOB:高效定时任务管理

一、前言 在现代应用程序中,定时任务是不可或缺的一部分。Spring Boot 和 XXL-Job 为你提供了一个强大的工具组合,以简化任务调度和管理。本文将带领你探索如何将这两者集成在一起,实现高效的定时任务管理。无论你是初学者还是有经验的开发者…

java-spring

入门案例 通过bean创建对象 先通过spring的ClassPathXmlApplicationContext读取xml文件 ,然后通过getbean()函数获取对象,进行操作通过反射机制,吸纳Class的函数forName(class属性)创建对象,然后clazz.getDeclaredConstructor().newinstanc…

springboot @value

#springboot value value 可以读取 yaml 中 的数据

简单爬虫框架实现

1. 框架功能概述 (1) HttpSession 类:请求管理 功能:封装 requests 库,实现带重试机制的 HTTP 请求(GET/POST)。关键特性: 自动处理 429(请求过多)、5xx(服务器错误&am…

欢乐熊大话蓝牙知识14:用 STM32 或 EFR32 实现 BLE 通信模块:从0到蓝牙,你也能搞!

🚀 用 STM32 或 EFR32 实现 BLE 通信模块:从0到蓝牙,你也能搞! “我能不能自己用 STM32 或 EFR32 实现一个 BLE 模块?” 答案当然是:能!还能很帅! 👨‍🏭 前…

网络攻防技术六:拒绝服务攻击

文章目录 一、拒绝服务攻击概述1、按攻击目标分类2、按攻击方式分类3、按受害者类型分类4、按攻击是否直接针对受害者分类5、按属性分类6、按舞厅分类7、按攻击机制分类 二、剧毒包型拒绝服务攻击1、碎片攻击2、Ping of Death攻击(ICMP Bug攻击)3、Land攻击4、循环攻…

阿里云无影云桌面深度测评

阿里云无影桌面深度测评:解锁云端工作“新范式”的“未来之钥”! 在数字化浪潮席卷全球的2025年,远程办公与混合办公已不再是权宜之计,而是职场不可逆转的新常态。然而,如何确保员工无论身在何处,都能拥有…

R²AIN SUITE AI知识库助力中国制造业数字化转型

一、市场现状:理性增长中的结构性机遇 走进2025年的中国制造业车间,你会看到这样的矛盾图景:一边是机器人手臂精准焊接的火花四溅,另一边是老师傅对着五套不同系统的屏幕皱眉记录数据。这种割裂感正是当前数字化转型深水区的真实…

Java函数式编程(下)

四、实际应用 1. 数据统计分析 示例1:商品订单数据统计分析 package com.itheima.day4.analysis;import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.time.YearMonth; import java.ti…

Java 检查一条线是否与圆接触或相交(Check if a line touches or intersects a circle)

给定一个圆的圆心坐标、半径 > 1 的圆心坐标以及一条直线的方程。任务是检查给定的直线是否与圆相交。有三种可能性: 1、线与圆相交。 2、线与圆相切。 3、线在圆外。 注意:直线的一般方程是 a*x b*y c 0,因此输入中只给出常数 a、b、…

typescript的Interface和Type

类型别名和接口非常相似,在大多数情况下你可以在它们之间自由选择。 几乎所有的 interface 功能都可以在 type 中使用,关键区别在于不能重新开放类型以添加新的属性,而接口始终是可扩展的。 // window.ts.transpileModule(src, {}); 这是调…

(17)课36:窗口函数的例题:例三登录时间与连续三天登录,例四球员的进球时刻连续进球。

(89)例三登录时间 : 保留代码版本 : CREATE TABLE sql_8( user_id varchar(2), login_date date ); insert into sql_8(user_id,login_date) values(A,2024-09-02),(A,2024-09-03),(A,2024-09-04),(B,2023-11-25),(B,2023-12- 3…

go语言基础|slice入门

slice slice介绍 slice中文叫切片,是go官方提供的一个可变数组,是一个轻量级的数据结构,功能上和c的vector,Java的ArrayList差不多。 slice和数组是有一些区别的,是为了弥补数组的一些不足而诞生的数据结构。最大的…