ITEEDU

我们来讨论最后一个问题:如何在一个字符串中间插入一个新字符串?
不知道大家能否独立地编制出一般方法的程序,笔者在下面给出了一个:

INSSTR.ASM
        data  segment
              assume    ds:data
      string  db        The same is presented to all those who love sunshine',0dh,0ah,24h
      buffer  db        9 dup	(?)
substrg	db'sunshi	ne',0	
        data  ends
			
        code  segment
              assume    cs:code
        main  proc      far
              mov       ax,data	;初始化DS寄存器指向数据段
              mov       ds,ax
              mov       dx,offset string	;显示源字符串
              mov       ah,9
              int       21h
              mov       bx,9	;在源字符串的第九个字符位置插入新串
              call      move_str	;调用MOVE_STR子过程空出新串的位置
              int       21h	;显示源字符串
              mov       bx,offset substrg	;BX寄存器指向将插入的子串
              mov       di,9	;DI寄存器指向插入点
              call      insert_str	;调用INSERT_STR将子串插入源串
              int       21h	;显示处理后的字符串
              mov       ah,4ch	;结束程序
              int       21h
        main  endp
			
    move_str  proc      near	;MOVE_STR子过程
              push      si	;保存SI寄存器
              push      ax	;保存AX寄存器
              mov       si,offset string	;SI寄存器指向源字符串
              add       si,bx	;计算字符串的分离点
              cmp       byte ptr [si],'$'	;分离点是字符串结尾吗?
              jnz       not_end	;若不是结尾转NOT_END
              mov       al,byte ptr [si]	;取得最后的'$'字符
              mov       byte ptr [si+8],al	;将此字符后移8个字节
              jmp       return	;返回主过程
    not_end:
              push      bx	;BX寄存器压入堆栈
              inc       bx	;BX寄存器加1
              call      move_str	;递归调用自身
              pop       bx	;取回BX寄存器
              mov       al,byte ptr [bx]	;取得BX寄存器指向的字符
              mov       byte ptr [bx+8],al	;将此字符后移8个字节
     return:
              pop       ax	;恢复AX寄存器
              pop       si	;恢复SI寄存器
              ret       ;返回主过程
    move_str  endp
			
  insert_str  proc      near	;INSERT_STR子过程
              push      ax	;保存AX寄存器
            insert_loop:
              cmp       byte ptr [bx],0	;BX寄存器指向子串末尾了吗?
              jz        quit	;若BX指向子串末尾则返回主程序
              mov       al,byte ptr [bx]	;取得BX寄存器指向的字符
              mov       byte ptr [di],al	;存入DI寄存器指向的位置
              inc       bx	;BX寄存器指向子串中下一个字符
              inc       di	;DI寄存器指向源串中下一个位置
              jmp       insert_loop	;插入下一个字符
       quit:
              pop       ax	;恢复AX寄存器
              ret       ;返回主过程
    insert_s  endp
        code  ends
              end       main

解决这个问题的一般思路是这样的:首先将插入点后面的字符串向后移动8字节距离,为"SUNSHINE"空出位置,然后将"SUNSHINE"分8个字节移入插入点。程序中的"MOVE_STR"子过程就是用于移动插入点后面的字符串的,在这个子过程中我们特别采用了"递归"算法,图5-4给出的部分堆栈的情况。INSERT_STR子过程用于将子串移动到插入点位置,从而完成了最终的插入工作。

可见这个问题的核心是两次将字符串移动位置。我们刚刚学过CMPS指令,它可以直接比较在存储器中的两串数据,那么有没有串指令可以直接在内存中移动数据串呢?

回答是肯定的。请看程序INSSTR1.ASM:

INSSTR1.ASM
        data  segment
              assume    ds:data
      string  db        'The sa	me is presented to all those who love sunshine',0dh,0ah,24h
      buffer  db        9 dup	(?)
     substrg  db        'sunshi	ne'
        data  ends
			
        code  segment
              assume    cs:code
        main  proc      far
              mov       ax,data	;初始化DS寄存器
              mov       ds,ax
              mov       dx,offset string	;DX寄存器指向字符串STRING
              mov       ah,9	;显示字符串
              int       21h
              mov       bx,8	;BX寄存器设置字符串中间空出的字符数
              call      move_str	;调用MOVE_STR子过程分离字符串
              int       21h	;显示分离后的字符串
              mov       bx,offset substrg	;BX寄存器指向子串
              mov       di,9	;DI寄存器设置插入点
              call      insert_str	;调用INSERT_STR子过程插入子串
              int       21h	;显示完整的字符串
              mov       ah,4ch	;结束程序
              int       21h
        main  endp
    move_str  proc      near	;MOVE_STR子过程
              push      ax	;保存将要使用的寄存器
              push      cx
              push      si
              push      di
              push      es
              mov       ax,data	;设置ES寄存器指向数据段
              mov       es,ax
              mov       si,offset string+55	;SI寄存器指向源字符串末尾
              mov       di,si
              add       di,bx	;DI寄存器指向源串末尾后第八个字节处
              mov       cx,47	;移动47个字符
              std       ;设置移动方向为从后向前
              rep       movsb	;移动字符
              pop       es	;恢复入口处保存的寄存器
              pop       di
              pop       si
              pop       cx
              pop       ax
              ret       ;返回主过程
    move_str  endp
  insert_str  proc      near	;INSERT_STR子过程
              push      ax	;保存将要使用的寄存器
              push      cx
              push      si
              push      di
              push      es
              mov       ax,data	;设置ES寄存器指向数据段
              mov       es,ax
              mov       si,bx	;SI寄存器指向待插入的子串
              add       di,offset string	;DI寄存器指向源串中的插入点
              mov       cx,8	;移动的字符数送入CX寄存器
              cld       ;设置移动方向为从前向后
              rep       movsb	;移动8个字符
              pop       es	;恢复入口处保存的寄存器
              pop       di
              pop       si
              pop       cx
              pop       ax
              ret       ;返回主过程
  insert_str  endp
        code  ends
              end       main

新增的MOVS指令就是直接在存储器中移动数据的一个串处理指令:

助记符:MOVS(Move String)
用 途:在内存中直接移动成串数据
格 式:MOVSB(移动一字节)
MOVSW(移动一个字)
执 行:将DS:SI处的一个字节(或字)直接传送到ES:DI处,同时SI,DI寄存器根据DF的状态相应增减。

 这个指令无论从形式上看还是从执行的操作上看都没有更多值得深入讨论的。只有一点需要注意,这个指令实际执行一种"COPY"的操作,数据串移动到另一个位置后,原位置的数据依然存在,这可以从上面程序的输出结果上看出来。
通过这么多实际问题我们可以看出,很多串处理指令只有和REP前缀联合应用才能真正处理数据串,单独应用的串指令目前只有一个--LODS。其实LODS也可以加上REP前缀,只是我还没有发现这样应用LODS指令有什么实用价值。我曾在程序中写出过REP PUSH AX这样的指令,竟然也能编译通过,不过用DEBUG跟踪执行这样的指令时并未发生想象中的所谓"连续压栈"所操作,大家可以试一下,看看究竟会发生什么。
问题已经讨论完了,但串处理指令还有一个没有说到,笔者特意将此指令保留到最后,目的是想借着讨论这个指令之机为即将开始的新一章打一个伏笔。这最后一个串指令就是和LODS相对的指令--STOS:

助记符:STOS(Store in to string)
用 途:成串存入数据
格 式:STOSB(存入一字节)
STOSW(存入一个字)
执 行:将累加器中的数据存入ES:DI指向的内存单元,同时DI寄存器根据DF的状态相应增减。

例程DISPSTR.ASM序说明这个指令的用法:

DISPSTR.ASM
        data  segment
              assume    ds:data
      string  db        'The sa	me sunshine is presente	d to all those who love sunshine',0
        data  ends
			
        code  segment
              assume    cs:code
        main  proc      far
              mov       ax,data	;初始化DS寄存器
              mov       ds,ax
              mov       ax,0b800h	;ES寄存器置入段地址0B800H
              mov       es,ax
              mov       di,0	;DI寄存器清0
              mov       ax,0720h	;AX寄存器置入0720H
              mov       cx,2048	;处理2048个字
              cld       ;设置DF标志
              rep       stosw	;将0720H写入ES:DI指向的内存单元
              mov       si,offset string	;SI寄存器指向字符串STRING
              mov       di,160*10+16	;设置DI寄存器
              mov       ah,2	;AH寄存器送入2
   disploop:
              lodsb     ;取得一个字符
              or        al,al	;是0吗?
              jz        exit	;是0,结束程序
              stosw     ;将字符连同AH中的02H一同写入内存
              jmp       disploop	;处理下一个字符
       exit:
              mov       ah,4ch	;结束程序
              int       21h
        main  endp
        code  ends
              end       main

 这个程序将一个字符串传送到0B800:1616处,每两个字符之间有一个数据02H。再传送字符串数据之前,程序先在0B800:0处开始的4KB存储器中填了一串"0720H"。用DEBUG跟踪这个程序,注意比较一下有REP前缀的STOS指令与没有前缀的STOS有何不同。
此程序的输出结果很吸引人,如果显示器上满是文字,那么执行这个程序后显示器上的杂乱信息将被一扫而光,同时屏幕上会出现一行文字,可不要被久违了的彩色惊呆了哟。
这个程序只是向内存中的一个特定位置存放了一些数据,为什么屏幕上会出现彩色的文字?文字的位置和ES:DI中的地址有关系吗?每两个ASCII码之间的数字02H又是什么呢?所有这些问题将在下一章详细地讨论。

本章结束语

到这一章为止我们已经了解了8086/88所有的寄存器,这一章的要点有以下这样几点:
(1)子过程数据传递的一般方式;
(2)特殊的子过程;
(3)串处理指令的特性;
(4)8086/88的所有寄存器的特点及应用。
再次提醒大家注意,汇编语言与操作系统及硬件的关系十分密切,如果不能将这三者好好结合起来无论如何是无法掌握汇编语言的。