ITEEDU

在实际应用中这个前缀往往采用这样两种形式:

REPE/REPZ-REPNE/REPNZ

指令中的"E"和"Z"与前面讨论的条件转移指令"JE/JZ"和"JNE/JNZ"中的"E"、"Z"含义相同。REPE的意思就是"Repeat While equal"--相等则重复,NE自然是"Not Equal"了。在前面的程序中我们使用了"REPNZ"作为前缀,因为我们要寻找一个与AL寄存器相等的数据,自然要在"不相等"的情况下重复SCASB指令,而一旦找到相等的数据,即使CX不为0,REP也会停止。这样做可以提高程序的效率,避免无意义的搜索。如果我们要在一串相同的数据中寻找一个与众不同者,那么自然要用"REPE"或"REPZ"作前缀了。

 由于SCAS指令的实际动作是将串中的数据与累加器进行比较,"找到"意味着ZF=1,"找不到"对应ZF=0,因此我们可以用"JZ/JNZ"指令来检测扫描结果。注意在计算"$"的位置时我们让DI寄存器多减了1,想一想这是为什么。

至此我们已经可以在一个串中寻找到一个字节或一个字的数据,下面我们来讨论一个更复杂的问题--如何在一个字符串中找到一个"子字符串"?
按照前面的习惯,我们先看看一般的解决办法:

SCHSTR.ASM
        data  segment
              assume    ds:data
string	db'The sa	me sunshine is presented to all those who love sunshine',0	
substrg	db'sunshi	ne',0	
ascii	db'012345	6789ABCDEF'	
        data  ends
			
        code  segment
              assume    cs:code
        main  proc      far
              mov       ax,data	;初始化DS寄存器指向数据段
              mov       ds,ax
              mov       di,offset string	;DI寄存器指向字符串STRING
   next_str:
              mov       si,offset substrg	;SI寄存器指向待查字符串
              lodsb     ;取得待查字符串的第一个字母
     search:
              cmp       al,byte ptr [di]	;将取到的字符与DI指向的字符进行比较
              jz        nextchar	;若相同则继续比较其余字符
              inc       di	;不相同则DI寄存器指向下一个字符
              cmp       byte ptr [di],0	;DI寄存器已经指向字符串末尾了吗?
              jnz       search	;未到末尾,转SEARCH继续比较
              jmp       exit	;已到末尾,转EXIT结束程序
   nextchar:
              mov       bx,di	;BX寄存器取得字符串指针
   cmp_next:
              cmp       byte ptr [si],0	;已经比完所有字符了吗?
              jz        out_di	;比完所有字符,转OUT_DI输出DI的值
              lodsb     ;取得待查字符串的下一个字母
              inc       bx	;BX中的指针加1,指向下一个字符
              cmp       al,byte ptr [bx]	;比较两个字符
              jz        cmp_next	;两字符相同,转CMP_NEXT比下一个字符
              jmp       continue	;两字符不同,转CONTINUE
     out_di:  mov       ax,di	;DI寄存器中的指针送入AX寄存器
              sub       ax,offset string	;计算待查字符串在源串中的偏移位置
              mov       bx,offset ascii	;BX寄存器指向ASCII表
              mov       cx,4	;输出4个数位
    outloop:  rol       ax,1	;AL寄存器循环左移4位
              rol       ax,1
              rol       ax,1
              rol       ax,1
              push      ax	;暂存AX寄存器
              and       ax,000fh	;保留AX寄存器的低4位
              xlat      ;取得对应的ASCII码
              mov       ah,0eh	;利用10H中断的0EH功能
              int       10h	;输出AL寄存器中的字符
              pop       ax	;取回AX寄存器
              loop      outloop	;处理下一个数位
              mov       ax,0e0dh	;输出回车符
              int       10h
              mov       al,0ah	;输出换行符
              int       10h
   continue:
              inc       di	;DI寄存器指向下一个字符
              jmp       next_str	;转NEXT_STR继续搜索待查串的首字符
       exit:  mov       ah,4ch	;结束程序
              int       21h
              endp
        main  ends
        code  end       main

这个程序首先在给定字符串中寻找子串的第一个字符,找到后将后面的字符与子串进行比较,若后面的字符均相同则输出首字符的偏移量;否则继续在剩余的字符中寻找。找到第一个子串后程序并未结束,而是继续在源字符串中搜索,直至整个字符串全部搜索完毕。不难看出这个程序的结构不很理想,我们用了许多的转移指令,使程序的流程显得比较混乱。那么用串处理指令能否使程序得以简化呢?请看下面的程序:

SCHSTR1.ASM
        data  segment
              assume    ds:data
string	db'The sa	me sunshine is presented to all those who love sunshine',0	
substrg	db'sunshi	ne'	
ascii	db'012345	6789ABCDEF'	
        data  ends
			
        code  segment
              assume    cs:code
        main  proc      far
              mov       ax,data	;初始化DS、ES寄存器指向数据段
              mov       ds,ax
              mov       es,ax
              mov       di,offset string	;DI寄存器指向源字符串
  next_char:
              cmp       byte ptr [di],0	;DI寄存器指向源字符串末尾了吗?
              jz        exit	;若DI寄存器已指向末尾则结束程序
              push      di	;暂存DI寄存器
              mov       si,offset substrg	;SI寄存器指向待查字符串
              mov       cx,8	;比较8个字符
        repz  cmpsb     ;将待查字符串与DI指向的8个字符相比
              pop       di	;恢复DI寄存器
              jcxz      out_di	;若8个字符均相同则输出DI寄存器的指针
              inc       di	;DI寄存器指向下一个字符
              jmp       next_char	;转NEXT_CHAR继续比较
     out_di:  mov       ax,di	;DI寄存器中的指针送入AX寄存器
              sub       ax,offset string	;计算待查字符串在源串中的偏移位置
              mov       bx,offset ascii	;BX寄存器指向ASCII表
              mov       cx,4	;输出4个数位
    outloop:  rol       ax,1	;AL寄存器循环左移4位
              rol       ax,1	;暂存AX寄存器
              rol       ax,1
              rol       ax,1
              push      ax
              and       ax,000fh	;保留AX寄存器的低4位
              xlat      ;取得对应的ASCII码
              mov       ah,0eh	;利用10H中断的0EH功能
              int       10h	;输出AL寄存器中的字符
              pop       ax	;取回AX寄存器
              loop      outloop	;处理下一个数位
              mov       ax,0e0dh	;输出回车符
              int       10h
              mov       al,0ah	;输出换行符
              int       10h
              inc       di	;DI寄存器指向下一个字符
              jmp       next_char	;转NEXT_CHAR继续搜索待查串的首字符
       exit:  mov       ah,4ch	;结束程序
              int       21h
        main  endp
        code  ends
              end       main

在这个程序我们又新增了两条指令--CMPS和JCXZ:

助记符:CMPS(Compare string)
用 途:比较两个数据串
格 式:CMPSB(按字节比较)
CMPSW(按字比较)
执 行:将位于DS:SI处的数据和位于ES:DI处的数据进行比较操作,并根据比较结果设定标志位,而后SI,DI两寄存器根据DF标志的状态相应增减。

相信大家对指令末尾的字母"B"和"W"已经有所领悟,不再多说。这条指令的特点是直接对存储器中的数据进行操作,和SCAS指令一样,如果不加REP前缀,那么这个指令也只能对一对数据进行比较,不能比较整串数据。因此在程序中我们使用了REPZ,目的是在两字符相同的情况下继续比较下一个字符。可以想到如果两字符串完全相同,那么这种比较会导致两种后果--ZF=1且CX=0。我们完全可以用"JZ/JNZ"指令检测比较的结果,但在上面的程序中我们使用了一条新指令:

助记符:JCXZ(Jump if cx is zero)
用 途:根据CX寄存器的情况进行转移
格 式:JCXZ 目的地址
执 行:判断CX寄存器情况,若CX=0则转移到目的地址处执行指令,否则继续执行下面的指令。

从本质上讲"JCXZ"也是条件转移指令,但它的转移条件有些不寻常,并非受标志寄存器控制,而直接受控于CX寄存器。当然,既然是条件转移指令,它也只能是短程的。和JZ指令不同,它没有转移条件相反的指令,JCXNZ是不存在的。在上面的程序中将JCXZ指令换成JZ指令也是可以的,只不过是取了不同的转移条件罢了。

从思路上看这两个程序并无区别,但使用串处理指令的程序要比采用一般方法的程序简捷一些,主要是少了一些易混乱的转移指令。学到这里我们应该对串处理指令的特性有了明确的认识,"串处理"的核心主要在于这样一些硬件:累加器、计数器、DS:SI寄存器组合、ES:DI寄存器组合、DF标志。同时还有一个指令前缀--REP。