Skip to content

REP & STOS

在汇编语言中,REP 和 STOS 是执行大块内存操作(比如初始化数组、清空缓冲区)时的“黄金搭档”。它们属于字符串操作指令集。

简单来说,STOS 负责“往内存里填一个数”,而 REP 负责“重复这件事很多次”。


一、 STOS 指令 (Store String)

STOS 的全称是 Store String。它的功能是将累加器(RAX/EAX/AX/AL)中的值写入到由 RDI 指向的内存地址中。

1. 自动化的三步走

当你执行一次 STOS 指令(以 64 位 STOSQ 为例)时,CPU 会自动完成以下动作: 1. 写入:将 RAX 的值存入内存地址 [RDI]。 2. 更新指针:根据 DF(方向标志位) 的值,自动修改 RDI: * 如果 DF = 0 (CLD),RDI = RDI + 8(指向下一个位置)。 * 如果 DF = 1 (STD),RDI = RDI - 8(指向前一个位置)。

2. 四种尺寸变体

根据你想一次写入多少数据,有四种具体的指令: * STOSB (Byte):写入 1 字节,使用 AL,RDI \(\pm\) 1。 * STOSW (Word):写入 2 字节,使用 AX,RDI \(\pm\) 2。 * STOSD (Double Word):写入 4 字节,使用 EAX,RDI \(\pm\) 4。 * STOSQ (Quad Word):写入 8 字节,使用 RAX,RDI \(\pm\) 8(仅限 64 位)。


二、 REP 前缀 (Repeat)

REP 是一个指令前缀,它可以放在任何字符串指令(如 STOS, MOVS)的前面。

1. 工作原理

REP 就像一个硬件层面的 while 循环: * 循环条件:只要 RCX(计数器寄存器)不等于 0,就重复执行后面的指令。 * 自动递减:每执行完一次后面的指令,CPU 会自动执行 RCX = RCX - 1。


三、 组合技:REP STOS

当你把它们写在一起时,就实现了一个极高性能的 memset(内存设置)操作。

典型应用场景:清空一块内存区域

假设你想把内存地址 buffer 开始的 800 个字节全部清零:

cld                 ; 1. 务必清空 DF,确保 RDI 是向高地址增加的
mov al, 0           ; 2. 设置要填充的值(这里是 0)
mov rdi, buffer     ; 3. 设置目标起始地址
mov rcx, 800        ; 4. 设置要填充的次数(800次)
rep stosb           ; 5. 核心:重复 800 次“写入 AL 并将 RDI 加 1”

为什么这比写 loop 快? 如果你用 loop 指令写:

label:
    mov [rdi], al
    inc rdi
    loop label
这涉及多次指令解码、分支预测。而 rep stos 是由 CPU 内部微码优化的,现代 CPU 甚至会使用专门的硬件加速(称为 ERMS,Enhanced REP MOVSB/STOSB),速度极快。


四、 重点总结与注意事项

  1. 寄存器绑定:
    • 值 必须在 RAX 系列寄存器里。
    • 地址 必须在 RDI(Destination Index)里。
    • 次数 必须在 RCX(Counter)里。
  2. DF 标志位: 在使用 REP STOS 之前,一定要用 CLD 确保方向正确。如果 DF 意外为 1,你的程序会从起始地址开始往回(低地址)写,这通常会导致内存越界崩溃。
  3. 效率建议: 如果你要填充大量内存,尽量使用 STOSQ (一次8字节) 而不是 STOSB (一次1字节),这样可以减少循环次数,显著提升性能。

五、 拓展:还有哪些 REP 家族成员?

除了 REP STOS,你可能还会见到: * REP MOVS:内存块拷贝(相当于 memcpy)。从 RSI 拷贝到 RDI。 * REPE CMPS:重复比较两块内存,直到发现不同或计数结束。 * REPNE SCAS:在内存块中搜索某个特定字符。

小练习: 如果你要把一个包含 100 个 32 位整数(int)的数组全部初始化为 0xFFFFFFFF,你会如何设置 EAX, RCX 和 RDI?你应该使用 STOSB 还是 STOSD?