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 指令写:
rep stos 是由 CPU 内部微码优化的,现代 CPU 甚至会使用专门的硬件加速(称为 ERMS,Enhanced REP MOVSB/STOSB),速度极快。
四、 重点总结与注意事项
- 寄存器绑定:
- 值 必须在
RAX系列寄存器里。 - 地址 必须在
RDI(Destination Index)里。 - 次数 必须在
RCX(Counter)里。
- 值 必须在
- DF 标志位:
在使用
REP STOS之前,一定要用CLD确保方向正确。如果DF意外为 1,你的程序会从起始地址开始往回(低地址)写,这通常会导致内存越界崩溃。 - 效率建议:
如果你要填充大量内存,尽量使用
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?