字符串处理指令
x86 架构提供了专门的字符串处理指令,可以高效地操作内存中的数据块。本章介绍这些指令的使用方法。
字符串指令概述
字符串指令用于处理内存中的连续数据,包括:
- 数据传送:MOVS
- 比较操作:CMPS
- 扫描操作:SCAS
- 存储操作:STOS
- 加载操作:LODS
这些指令通常与重复前缀(REP、REPE、REPNE)配合使用。
方向标志
DF 标志的作用
方向标志(DF)决定字符串操作的方向:
- DF = 0:递增方向(ESI/EDI 增加)
- DF = 1:递减方向(ESI/EDI 减少)
设置方向标志
cld ; DF = 0,递增方向
std ; DF = 1,递减方向
数据传送指令
MOVS 指令
MOVS 指令在内存之间传送数据:
movsb ; 传送字节:[ES:DI/RDI] = [DS:SI/RSI]
movsw ; 传送字(16 位)
movsd ; 传送双字(32 位)
movsq ; 传送四字(64 位)
; 根据方向标志更新指针
; DF=0:RSI++, RDI++
; DF=1:RSI--, RDI--
REP 前缀
REP 前缀重复执行指令,直到 RCX/ECX 为 0:
; 内存复制
mov rsi, source ; 源地址
mov rdi, dest ; 目标地址
mov rcx, count ; 字节数
cld ; 递增方向
rep movsb ; 复制 count 个字节
; 复制双字(更快)
mov rcx, count / 4 ; 双字数
rep movsd ; 复制 count/4 个双字
内存复制示例
; void memcpy(void *dest, const void *src, size_t n)
; 参数:RDI = dest, RSI = src, RDX = n
memcpy:
mov rcx, rdx ; 字节数
test rcx, rcx
jz .done
cld
rep movsb
.done:
ret
比较指令
CMPS 指令
CMPS 指令比较两个内存区域:
cmpsb ; 比较字节:[RSI] - [RDI]
cmpsw ; 比较字
cmpsd ; 比较双字
cmpsq ; 比较四字
; 设置标志位,然后更新指针
REPE/REPZ 前缀
当相等时继续重复:
; 比较两个字符串
mov rsi, str1
mov rdi, str2
mov rcx, length
cld
repe cmpsb ; 比较直到不相等或计数结束
; 检查结果
ja str1_greater ; str1 > str2
jb str2_greater ; str1 < str2
; 相等
REPNE/REPNZ 前缀
当不相等时继续重复:
; 查找不同的字符
mov rsi, str1
mov rdi, str2
mov rcx, length
cld
repne cmpsb ; 比较直到相等或计数结束
内存比较示例
; int memcmp(const void *s1, const void *s2, size_t n)
; 返回:<0, 0, >0
memcmp:
mov rcx, rdx ; 字节数
test rcx, rcx
jz .equal
cld
repe cmpsb
ja .greater
jb .less
.equal:
xor eax, eax
ret
.greater:
mov eax, 1
ret
.less:
mov eax, -1
ret
扫描指令
SCAS 指令
SCAS 指令在字符串中扫描特定值:
scasb ; 比较 AL 与 [RDI],更新 RDI
scasw ; 比较 AX 与 [RDI]
scasd ; 比较 EAX 与 [RDI]
scasq ; 比较 RAX 与 [RDI]
字符串长度计算
; size_t strlen(const char *s)
; 参数:RDI = 字符串地址
; 返回:RAX = 长度
strlen:
xor eax, eax ; 计数器
xor ecx, ecx
dec ecx ; ECX = 0xFFFFFFFF
cld
repne scasb ; 扫描直到找到 0
not ecx ; 取反得到长度
dec ecx ; 减 1(不包括 null)
mov eax, ecx
ret
字符查找
; char *strchr(const char *s, int c)
; 参数:RDI = 字符串地址,ESI = 要查找的字符
; 返回:RAX = 指向字符的指针,或 NULL
strchr:
mov al, sil ; 要查找的字符
mov rcx, -1 ; 无限计数
cld
repne scasb
jne .not_found
lea rax, [rdi - 1] ; 指向找到的字符
ret
.not_found:
xor eax, eax
ret
存储指令
STOS 指令
STOS 指令将寄存器值存储到内存:
stosb ; [RDI] = AL,更新 RDI
stosw ; [RDI] = AX
stosd ; [RDI] = EAX
stosq ; [RDI] = RAX
内存填充
; void *memset(void *s, int c, size_t n)
; 参数:RDI = 目标地址,ESI = 填充值,RDX = 字节数
memset:
mov rcx, rdx ; 字节数
test rcx, rcx
jz .done
mov al, sil ; 填充值
cld
rep stosb
.done:
mov rax, rdi ; 返回目标地址
sub rax, rdx
ret
清零内存
; 清零 1024 字节缓冲区
mov rdi, buffer
mov rcx, 1024 / 8 ; 128 个四字
xor eax, eax ; 填充 0
cld
rep stosq
加载指令
LODS 指令
LODS 指令从内存加载到寄存器:
lodsb ; AL = [RSI],更新 RSI
lodsw ; AX = [RSI]
lodsd ; EAX = [RSI]
lodsq ; RAX = [RSI]
使用示例
; 将字符串转换为大写
mov rsi, str
mov rdi, str
mov rcx, length
cld
.upper_loop:
lodsb ; 加载字符到 AL
cmp al, 'a'
jb .store
cmp al, 'z'
ja .store
sub al, 32 ; 转大写
.store:
stosb ; 存储字符
loop .upper_loop
重复前缀总结
| 前缀 | 条件 | 用途 |
|---|---|---|
| REP | RCX != 0 | 重复执行 |
| REPE/REPZ | RCX != 0 且 ZF = 1 | 相等时重复 |
| REPNE/REPNZ | RCX != 0 且 ZF = 0 | 不相等时重复 |
字符串指令组合
字符串复制并转换
; 复制字符串并转换为大写
; 参数:RSI = 源,RDI = 目标
strcpy_upper:
cld
.copy_loop:
lodsb ; 加载字符
test al, al ; 检查是否为 null
jz .done
cmp al, 'a'
jb .store
cmp al, 'z'
ja .store
sub al, 32 ; 转大写
.store:
stosb ; 存储字符
jmp .copy_loop
.done:
stosb ; 存储 null 终止符
ret
字符串拼接
; char *strcat(char *dest, const char *src)
; 参数:RDI = dest,RSI = src
strcat:
push rdi
; 找到 dest 的末尾
xor eax, eax
mov rcx, -1
cld
repne scasb
dec rdi ; 指向 null
; 复制 src 到 dest 末尾
.copy_loop:
lodsb
stosb
test al, al
jnz .copy_loop
pop rax ; 返回 dest
ret
字符串查找子串
; char *strstr(const char *haystack, const char *needle)
; 参数:RDI = haystack,RSI = needle
strstr:
push rbx
push r12
push r13
mov r12, rdi ; 保存 haystack
mov r13, rsi ; 保存 needle
; 空 needle 返回 haystack
mov al, [rsi]
test al, al
jz .found
.search:
; 在 haystack 中查找 needle 的第一个字符
mov rdi, r12
mov al, [r13]
mov rcx, -1
cld
repne scasb
jne .not_found
; 检查是否匹配整个 needle
mov r12, rdi ; 更新 haystack 位置
dec r12
mov rsi, r13 ; needle
inc rsi ; 跳过第一个字符(已匹配)
.compare_loop:
mov al, [rsi]
test al, al
jz .found ; needle 结束,找到匹配
mov bl, [rdi]
cmp al, bl
jne .search ; 不匹配,继续搜索
inc rsi
inc rdi
jmp .compare_loop
.found:
mov rax, r12
jmp .done
.not_found:
xor eax, eax
.done:
pop r13
pop r12
pop rbx
ret
性能考虑
字符串指令 vs 普通指令
现代处理器上,字符串指令的性能特点:
- 短数据块:普通循环可能更快
- 长数据块:字符串指令通常更快
- 对齐数据:对齐的数据处理更快
优化建议
; 优化:对齐后使用更大的数据单位
; 假设复制大量数据
; 先复制未对齐的部分
mov rcx, rdi
and rcx, 7 ; 检查低 3 位
jz .aligned
sub rcx, 8
neg rcx ; 需要复制的字节数
sub rdx, rcx ; 更新剩余字节数
rep movsb
.aligned:
; 以四字为单位复制
mov rcx, rdx
shr rcx, 3 ; 四字数
rep movsq
; 复制剩余字节
mov rcx, rdx
and rcx, 7
rep movsb
小结
本章介绍了 x86 的字符串处理指令:
- MOVS:内存块复制
- CMPS:内存块比较
- SCAS:字符串扫描
- STOS:内存填充
- LODS:内存加载
- 重复前缀:REP、REPE/REPZ、REPNE/REPNZ
这些指令在处理字符串和内存块时非常高效,是系统编程的重要工具。下一章将学习系统调用。