汇编语言Irvine32字符串过程详解[附带实例]

广告位

本节将演示用 Irvine32 链接库中的几个过程来处理空字节结束的字符串。这些过程与标准 C 库中的函数有着…

本节将演示用 Irvine32 链接库中的几个过程来处理空字节结束的字符串。这些过程与标准 C 库中的函数有着明显的相似性:

;将源串复制到目的串。
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_1

ShowString 过程用方括号显示了被裁剪后的字符串,这里未给出其代码。过程输出示例如下:

[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 中

Str_length 过程用 EAX 寄存器返回字符串长度,所以,后面的代码行将它与零进行比较,如果字符串为空,则跳过后续代码:

cmp eax,0    ;字符串长度等于零吗?
je L3             ;是:立刻退出

在继续后面的程序之前,先假设该字符串不为空。ECX 为循环计数器,因此要将字符串长度赋给它。由于希望 EDI 指向字符串最后一个字符,因此把 EAX(包含字符串长度)减 1 后再加到 EDI 上:

mov ecx,eax          ;否:ECX =字符串长度
dec eax
add edi,eax           ;指向最后一个字符

现在 EDI 指向的是最后一个字符,将该字符复制到 AL 寄存器,并与分隔符比较:

L1: mov al, [edi]    ;取字符
    cmp al, char      ;是分隔符吗?

如果该字符不是分隔符,则退出循环,并用标号为 L2 的语句插入一个空字节:

jne L2                   ;否:插入空字节

否则,如果发现了分隔符,则继续循环,逆向搜索字符串。实现的方法为:将 EDI 后退一个字符,再重复循环:

dec edi                 ;是:继续后退
loop L1                ;直到字符串的第一个字符

如果整个字符串都由分隔符组成,则循环计数器将减到零,并继续执行 loop 指令下面的代码行,即标号为 L2 的代码,在字符串中插入一个空字节:

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 程序的输出如下所示:

汇编语言Irvine32字符串过程详解[附带实例]

关于作者: 汇编语言

为您推荐

广告位

发表评论