汇编语言Irvine32字符串过程详解[附带实例]
;将源串复制到目的串。
Str_copy PROTO,
source:PTR BYTE,
target:PTR BYTE
;用 EAX 返回串长度(包括零字节)。
Str_length PROTO,
pString:PTR BYTE
;比较字符串 1 和字符串 2。
;并用与 CMP 指令相同的方法设置零标志位和进位标志位。
Str_compare PROTO,
string1:PTR BYTE,
string2:PTR BYTE
;从字符串尾部去掉特定的字符。
;第二个参数为要去除的字符。
Str_trim PROTO,
pString:PTR BYTE,
char:BYTE
;将字符串转换为大写。
Str_ucase PROTO,
pString:PTR BYTE
Str_compare 过程
Str_compare 过程比较两个字符串,其调用格式如下:INVOKE Str_compare, ADDR string1, ADDR string2
它从第一个字节开始按正序比较字符串。这种比较是区分大小写的,因为字母的大写和小写 ASCII 码不相同。该过程没有返回值,若参数为 string1 和 string2,则进位标志位和零标志位的含义如下表所示。关系 | 进位标志位 | 零标志位 | 为真则分支(指令) |
---|---|---|---|
string1 < string2 | 1 | 0 | JB |
string1 = string2 | 0 | 1 | JE |
string1 > string2 | 0 | 0 | JA |
回顾《CMP指令》一节中 CMP 指令如何设置进位标志位和零标志位。下面给出了 Str_compare 过程的代码清单。
;-------------------------------------- Str_compare PROC USES eax edx esi edi, string1:PTR BYTE, string2:PTR BYTE ;比较两个字符串。 ;无返回值,但是零标志位和进位标志位受到的影响与 CMP 指令相同。 ;-------------------------------------- mov esi, string1 mov edi, string2 L1: mov al, [esi] mov dl, [edi] cmp al, 0 ; string1 结束? jne L2 ; 否 cmp dl, 0 ; 是:string2 结束? jne L2 ; 否 jmp L3 ; 是,退出且ZF=1 L2: inc esi ; 指向下一个字符 inc edi ; 字符相等? cmp al,dl ; 是:继续循环 je L1 L3: ret ; 否:退出并设置标志位 Str_compare ENDP实现 Str_compare 时也可以使用 CMPSB 指令,但是这条指令要求知道较长字符串的长度,这样就需要调用 Str_length 程两次。
本例中,在同一个循环内检测两个字符串的零结束符显得更加容易。CMPSB 在处理长度已知的大型字符串或数组时最有效。
Str_length 过程
Str_length 过程用 EAX 返回一个字符串的长度。调用该过程时,要传递字符串的偏移地址。例如:INVOKE Str_length, ADDR myString
过程实现如下:Str_length PROC USES edi, pString:PTR BYTE ;指向字符串 mov edi, pString ;字符计数器 mov eax, 0 ;字符结束? L1: cmp BYTE PTR[edi],0 je L2 ;是:退出 inc edi ;否:指向下一个字符 inc eax ;计数器加1 jmp L1 L2: ret Str_length ENDP
Str_copy 过程
Str_copy 过程把一个空字节结束的字符串从源地址复制到目的地址。调用该过程之前,要确保目标操作数能够容纳被复制的字符串。Str_copy 的调用语法如下:INVOKE Str_copy, ADDR source, ADDR target
过程无返回值。下面是其实现:;-------------------------------------- Str_copy PROC USES eax ecx esi edi, source:PTR BYTE, ; source string target:PTR BYTE ; target string ;将字符串从源串复制到目的串。 ;要求:目标串必须有足够空间容纳从源复制来的串。 ;-------------------------------------- INVOKE Str_length, source ;EAX = 源串长度 mov ecx, eax ;重复计数器 inc ecx ;由于有零字节,计数器加 1 mov esi, source mov edi, target cld ;方向为正向 rep movsb ;复制字符串 ret Str_copy ENDP
Str_trim 过程
Str_trim 程从空字节结束字符串中移除所有与选定的尾部字符匹配的字符。其调用语法如下:INVOKE Str_trim, ADDR string, char_to_trim
这个过程的逻辑很有意思,因为程序需要检查多种可能的情况(以下用 # 作为尾部字符):1) 字符串为空。
2) 字符串一个或多个尾部字符的前面有其他字符,如“Hello#”。
3) 字符串只含有一个字符,且为尾部字符,如“#”。
4) 字符串不含尾部字符,如“Hello”或“H”。
5) 字符串在一个或多个尾部字符后面跟随有一个或多个非尾部字符,如“#H”或“##Hello”
使用 Str_trim 过程可以删除字符串尾部的全部空格(或者任何重复的字符)。从字符串中去掉字符的最简单的方法是,在想要移除的字符前面插入一个空字节。空字节后面的任何字符都会变得无意义。
下表列出了一些有用的测试例子。在所有例子中都假设从字符串中删除的是 # 字符,表中给出了期望的输出。
输入字符串 | 预期修改后的字符串 |
---|---|
“Hello##” | “Hello” |
“#” | “”(空字符串) |
“Hello” | "Hello” |
“H” | “H” |
“#H” | “#H” |
现在来看看测试 Str_trim 程的代码。INVOKE 语句向 Str_trim 传递字符串地址:
.data string_1 BYTE "Hello##",0 .code INVOKE Str_trim,ADDR string_1,'#' INVOKE ShowString,ADDR string_1ShowString 过程用方括号显示了被裁剪后的字符串,这里未给出其代码。过程输出示例如下:
[Hello]
下面给出了 Str_trim 的实现,它在想要保留的最后一个字符后面插入了一个空字节。空字节后面的任何字符一般都会被字符串处理函数所忽略。;------------------------------------- ;Str_trim ;从字符串末尾删除所有与给定分隔符匹配的字符。 ;返回:无 ;------------------------------------- Str_trim PROC USES eax ecx edi, pString:PTR BYTE, ;指向字符串 char: BYTE ;要移必的字符 mov edi,pString ;准备调用 Str_length INVOKE Str_length,edi ;用 EAX 返回鬆 cmp eax,0 ;长度是否为零? je L3 ;是:立刻退出 mov ecx, eax ;否:ECX = 字符串长度 dec eax add edi,eax ;指向最后一个字符 L1: mov al, [edi] ;取一个字符 cmp al,char ;是否为分隔符? jne L2 ;否:插入空字节 cec edi ;是:继续后退一个字符 loop L1 ;直到字符串的第一个字符 L2: mov BYTE PTR [edi+1 ],0 ;插入一个空字节 L3: ret Str_trim ENDP
详细说明
现在仔细研究一下 Str_trim。该算法从字符串最后一个字符开始,反向进行串扫描,以寻找第一个非分隔符字符。当找到这样的字符后,就在该字符后面的位置上插入一个空字节:ecx = length(str) if length (str) > 0 then edi = length - 1 do while ecx > 0 if str[edi] ≠ delimiter then str[edi+1] = null break else edi = edi - 1 end if ecx = ecx - 1 end do下面逐行查看代码实现。首先,pString 为待裁剪字符串的地址。程序需要知道该字符串的长度,Str_length 过程用 EDI 寄存器接收其输入参数:
mov edi,pString ;准备调用 Str_length
INVOKE Str_length,edi ;过程返回值在 Eax 中
cmp eax,0 ;字符串长度等于零吗?
je L3 ;是:立刻退出
mov ecx,eax ;否:ECX =字符串长度
dec eax
add edi,eax ;指向最后一个字符
L1: mov al, [edi] ;取字符
cmp al, char ;是分隔符吗?
jne L2 ;否:插入空字节
否则,如果发现了分隔符,则继续循环,逆向搜索字符串。实现的方法为:将 EDI 后退一个字符,再重复循环:
dec edi ;是:继续后退
loop L1 ;直到字符串的第一个字符
L2: mov BYTE PTR [edi+1], 0 ;插入空字节
假如程序控制到达这里的原因是循环计数减为零,那么,EDI 就会指向字符串第一个字符之前的位置。因此需要用表达式 [edi+1] 来指向第一个字符。在两种情况下,程序会执行标号 L2:
- 其一,在字符串中发现了非分隔符字符;
- 其二, 循环计数减为零。
标号 L2 后面是标号为 L3 的 RET 指令,用来结束整个过程:
L3: ret
Str_trim ENDP
Str_ucase 过程
Str_ucase 过程把一个字符串全部转换为大写字母,无返回值。调用过程时,要向其传 递字符串的偏移量:INVOKE Str_ucase, ADDR myString
过程实现如下:;--------------------------------- ;Str_ucase ;将空字节结束的字符串转换为大写字母。 ;返回:无 ;--------------------------------- Str_ucase PROC USES eax esi, pString:PTR BYTE mov esi,pString L1: mov al, [esi] ;取字符 cmp al, 0 ;字符串是否结束? je L3 ;是:退出 cnp al, 'a' ;小于"a" ? jb L2 cnp al, 'z' ;大于"z" ? ja L2 and BYTE PTR [esi], 11011111b ;转换字符 L2: inc esi ;下一个字符 jmp L1 L3: ret Str_ucase ENDP
字符串演示程序
下面的 32 位程序演示了对 Irivne32 链接库中 Str_trim、Str_ucase、Str_compare 和 Str_length 过程的调用:; String Library Demo (StringDemo.asm) ; 该程序演示了链接库中字符串处理过程 INCLUDE Irvine32.inc .data string_1 BYTE "abcde////",0 string_2 BYTE "ABCDE",0 msg0 BYTE "string_1 in upper case: ",0 msg1 BYTE "string1 and string2 are equal",0 msg2 BYTE "string_1 is less than string_2",0 msg3 BYTE "string_2 is less than string_1",0 msg4 BYTE "Length of string_2 is ",0 msg5 BYTE "string_1 after trimming: ",0 .code main PROC call trim_string call upper_case call compare_strings call print_length exit main ENDP trim_string PROC ; 从 string_1 删除尾部字符 INVOKE Str_trim, ADDR string_1,'/' mov edx,OFFSET msg5 call WriteString mov edx,OFFSET string_1 call WriteString call Crlf ret trim_string ENDP upper_case PROC ; 将 string_1 转换为大写字母 mov edx,OFFSET msg0 call WriteString INVOKE Str_ucase, ADDR string_1 mov edx,OFFSET string_1 call WriteString call Crlf ret upper_case ENDP compare_strings PROC ; 比较 string_1 和 string_2. INVOKE Str_compare, ADDR string_1, ADDR string_2 .IF ZERO? mov edx,OFFSET msg1 .ELSEIF CARRY? mov edx,OFFSET msg2 ; string 1 小于... .ELSE mov edx,OFFSET msg3 ; string 2 小于... .ENDIF call WriteString call Crlf ret compare_strings ENDP print_length PROC ; 显示 string_2 的长度 mov edx,OFFSET msg4 call WriteString INVOKE Str_length, ADDR string_2 call WriteDec call Crlf ret print_length ENDP END main调用 Str_trim 过程从 string_1 删除尾部字符,调用 Str_ucase 过程将字符串转换为大写字母。
String Library Demo 程序的输出如下所示: