汇编语言字符串处理.md

原始源文件

---
ingested: true
ingestedAt: 2026-05-17
---
标题: 汇编语言 - 字符串处理
UP主: runoob
链接: https://www.runoob.com/assembly/assembly-string.html
提取方式: webfetch
内容: ## 汇编语言 - 字符串处理

字符串处理是编程中常见的需求。

在汇编中处理字符串意味着直接操作内存中的字节序列,虽然更底层但也更灵活。

---

## 字符串的定义和存储

在 NASM 中,字符串本质上是字节序列,可以用 DB 伪指令定义:

## 实例

; 字符串定义方式

section .data  
; 方式1:标准字符串  
 str1 db 'Hello, runoob!', 0 ; C 风格:以 null 结尾

; 方式2:带换行符  
 str2 db 'Line1', 0xA, 'Line2', 0xA

; 方式3:反引号支持转义  
 str3 db `hello\nworld\n`, 0 ; 自动转换转义序列

; 方式4:逐个字符定义  
 str4 db 'A', 'B', 'C', 'D', 0

; 方式5:使用 dup 生成重复字符  
 border db 40 dup('-') ; 40 个减号

; 字符串长度计算(编译时)  
 str1_len equ $ - str1 ; 包含结尾 null  

---

## 计算字符串长度

有两种方式获取字符串长度:编译时计算(EQU)和运行时计算(遍历查找 null):

## 实例

; 文件路径:strlen_demo.asm  
; 两种计算字符串长度的方式

section .data  
; 方式A:编译时计算(适用于常量字符串)  
 msg1 db 'Hello, RUNOOB!', 0xA  
 msg1_len equ $ - msg1 ; 编译时自动计算

; 方式B:null 结尾的字符串(运行时计算)  
 msg2 db 'Find my length', 0 ; 以 0 结尾

section .text  
global _start

_start:  
; 方式A:直接使用编译时计算的长度  
mov eax, 4  
mov ebx, 1  
mov ecx, msg1  
mov edx, msg1_len ; 直接使用常量  
int 0x80

; 方式B:运行时计算 null 结尾字符串的长度  
mov esi, msg2 ; esi 指向字符串起始  
mov ecx, 0 ; 计数器

strlen_loop:  
cmp byte [esi], 0 ; 当前字节是 null?  
je strlen_done ; 是,结束  
inc ecx ; 计数 +1  
inc esi ; 指针 +1  
jmp strlen_loop

strlen_done:  
; ecx 现在存放字符串长度(不含 null)  
mov eax, 4  
mov ebx, 1  
mov ecx, msg2  
mov edx, ecx ; 使用计算出的长度  
; 这里有个问题:ecx 被覆盖了,应该先保存  
; 正确做法:push ecx 再 pop 到 edx

mov eax, 1  
mov ebx, 0  
int 0x80  

---

## 字符串复制

使用循环逐字节复制,或使用 x86 的字符串操作指令 `MOVSB`:

## 实例

; 文件路径:strcpy_demo.asm  
; 字符串复制两种实现方式

section .data  
 src db 'runoob source string', 0  
 src_len equ $ - src

section .bss  
 dest_manual resb 64 ; 手动复制目标  
 dest_fast resb 64 ; 快速复制目标

section .text  
global _start

_start:  
; 方式A:手动逐字节复制  
mov esi, src ; 源地址  
mov edi, dest_manual ; 目标地址  
mov ecx, src_len ; 字节数

copy_loop:  
mov al, [esi] ; 读取一字节  
mov [edi], al ; 写入一字节  
inc esi ; 源指针++  
inc edi ; 目标指针++  
loop copy_loop

; 方式B:使用字符串操作指令(更快)  
cld ; 清除方向标志(DF=0,正向复制)  
mov esi, src ; 源地址(ESI)  
mov edi, dest_fast ; 目标地址(EDI)  
mov ecx, src_len ; 字节数(ECX)  
rep movsb ; 重复执行 movsb ecx 次  
; rep movsb:while(ecx>0) { [edi]=[esi]; esi++; edi++; ecx--; }

; 验证:输出两个复制结果  
mov eax, 4  
mov ebx, 1  
mov ecx, dest_manual  
mov edx, src_len  
int 0x80

mov eax, 4  
mov ebx, 1  
mov ecx, dest_fast  
mov edx, src_len  
int 0x80

mov eax, 1  
mov ebx, 0  
int 0x80  

> `REP MOVSB` 是 x86 最经典的字符串复制方式。但在现代 CPU 上,手动复制循环配合循环展开可能比 REP MOVSB 更快,因为现代 CPU 对简单操作有更好的流水线优化。

---

## 字符串比较

使用 `CMPSB` 配合 `REPE`(重复直到不等)逐字节比较:

## 实例

; 文件路径:strcmp_demo.asm  
; 比较两个字符串是否相等

section .data  
 str_a db 'runoob', 0  
 str_b db 'runoob', 0  
 str_c db 'RUNOOB', 0  
 eq_msg db 'Strings are equal', 0xA  
 eq_len equ $ - eq_msg  
 ne_msg db 'Strings are NOT equal', 0xA  
 ne_len equ $ - ne_msg

section .text  
global _start

_start:  
cld ; 正向比较  
mov esi, str_a ; 第一个字符串  
mov edi, str_b ; 第二个字符串  
mov ecx, 7 ; 最多比较 7 字节(含 null)

repe cmpsb ; 重复比较直到不等或 ecx=0  
; repe: 如果 ZF=1(相等)且 ecx>0 则继续  
; cmpsb: 比较 [esi] 和 [edi],然后 esi++, edi++

je strings_equal ; 如果 ZF=1,所有字节都相等

; 不等  
mov eax, 4  
mov ebx, 1  
mov ecx, ne_msg  
mov edx, ne_len  
int 0x80  
jmp compare_next

strings_equal:  
mov eax, 4  
mov ebx, 1  
mov ecx, eq_msg  
mov edx, eq_len  
int 0x80

compare_next:  
; 比较 str_a 和 str_c(大小写不同)  
cld  
mov esi, str_a  
mov edi, str_c  
mov ecx, 7  
repe cmpsb  
jne not_equal_2

mov eax, 4  
mov ebx, 1  
mov ecx, eq_msg  
mov edx, eq_len  
int 0x80  
jmp exit

not_equal_2:  
mov eax, 4  
mov ebx, 1  
mov ecx, ne_msg  
mov edx, ne_len  
int 0x80

exit:  
mov eax, 1  
mov ebx, 0  
int 0x80  

---

## 字符串操作指令汇总

| 指令    | 功能                     | 使用的寄存器                       |
| ----- | ---------------------- | ---------------------------- |
| MOVSB | 复制字节:[EDI] = [ESI] | ESI=源, EDI=目标, ECX=次数, DF=方向 |
| MOVSW | 复制字(2 字节)              | 同上                           |
| MOVSD | 复制双字(4 字节)             | 同上                           |
| STOSB | 存字节:[EDI] = AL       | EDI=目标, AL=值, ECX=次数         |
| LODSB | 取字节:AL = [ESI]       | ESI=源                        |
| CMPSB | 比较字节:[ESI] - [EDI] | ESI=源, EDI=目标, ECX=次数        |
| SCASB | 扫描字节:AL - [EDI]      | EDI=目标, AL=查找值, ECX=次数       |

---

## 大小写转换示例

## 实例

; 文件路径:case_convert.asm  
; 将字符串中的小写字母转为大写

section .data  
 msg db 'Hello, runoob! Welcome to Assembly.', 0xA  
 len equ $ - msg

section .text  
global _start

_start:  
; 输出原始字符串  
mov eax, 4  
mov ebx, 1  
mov ecx, msg  
mov edx, len  
int 0x80

; 转换:遍历字符串,小写 -> 大写  
mov esi, msg ; 指向字符串开头  
mov ecx, len ; 循环次数

convert_loop:  
mov al, [esi] ; 读取字符  
cmp al, 'a' ; 是否 >= 'a'  
jb next_char ; 小于 'a',跳过  
cmp al, 'z' ; 是否 <= 'z'  
ja next_char ; 大于 'z',跳过

; 小写转大写:'a'(97) - 'A'(65) = 32  
sub al, 32 ; 转为大写  
mov [esi], al ; 写回

next_char:  
inc esi  
loop convert_loop

; 输出转换后的字符串  
mov eax, 4  
mov ebx, 1  
mov ecx, msg  
mov edx, len  
int 0x80

mov eax, 1  
mov ebx, 0  
int 0x80  

运行结果:

$ nasm -f elf32 case_convert.asm -o case_convert.o
$ ld -m elf_i386 case_convert.o -o case_convert
$ ./case_convert
Hello, runoob! Welcome to Assembly.
HELLO, RUNOOB! WELCOME TO ASSEMBLY.

> 方向标志 DF 决定了字符串操作的方向:DF=0 时 ESI/EDI 递增(正向),DF=1 时递减(反向)。用 `CLD` 清零 DF,用 `STD` 置位 DF。在调用 C 函数或系统调用前应该确保 DF=0(cdecl 要求)。