在实际应用中这个前缀往往采用这样两种形式:
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,想一想这是为什么。
至此我们已经可以在一个串中寻找到一个字节或一个字的数据,下面我们来讨论一个更复杂的问题--如何在一个字符串中找到一个"子字符串"?
按照前面的习惯,我们先看看一般的解决办法:
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
这个程序首先在给定字符串中寻找子串的第一个字符,找到后将后面的字符与子串进行比较,若后面的字符均相同则输出首字符的偏移量;否则继续在剩余的字符中寻找。找到第一个子串后程序并未结束,而是继续在源字符串中搜索,直至整个字符串全部搜索完毕。不难看出这个程序的结构不很理想,我们用了许多的转移指令,使程序的流程显得比较混乱。那么用串处理指令能否使程序得以简化呢?请看下面的程序:
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。