实验3 字符串/块处理程序设计

实验3  字符串/块处理程序设计

CPU提供了一些系列的指令,用于对内存中的连续数据单元进行处理。利用这些指令,可以编写出高效的程序,完成对字符串、数据块的操作处理。

3.1 搜索字符

块扫描指令SCASBSCASWSCASDEDI指向的目标数据块中查找ALAXEAX,然后EDI自动增加或减小124。块扫描指令像CMP指令那样设置FLAGS寄存器中的标志位。

块扫描指令的几个关键特征为:

1. 自动修改指针

块操作指令会自动地修改ESIEDI,使它们指向下一个源操作数和目的操作数。CPUEFLAGS中有一个标志位DF,由DF来决定ESIEDI是增加还是减小。DF=0时,地址增加;DF=1时,地址减小。SCASBSCASWSCASD只修改EDI

2. 方向标志

CLD指令将DF标志设为0,代表Clear Direction FlagSTD指令将DF标志设为1,代表Set Direction Flag

3. 操作数大小

ALAXEAX称为匹配数,扫描就是对目标数据块的每一个单元和匹配数相比较。

操作数的大小决定增加或减小的单位。对于字节操作数,增减量为1,指向下一个字节;对于字操作数,增减量为2,指向下一个字;对于双字操作数,增减量为4,指向下一个双字。

4. 重复前缀

SCAS指令可以和REPZREPNZ前缀一起使用,ECX是最大扫描次数,即数据块中单元的个数。

SCAS指令一般与REPNZ前缀配合使用。使用REPNZ前缀时,只有比较结果为不等(ZF=0)时,表示没有找到匹配数,继续进行下一次比较(扫描)。如果比较结果为相等(ZF=1),表示已经找到了匹配数,不再继续比较。

扫描完成后,根据ZF标志位来决定是否找到。ZF=1时,找到了匹配数,此时EDI指向匹配数位置的下一个单元,ECX是剩下的比较次数(ECX也有可能为0);ZF=0时,没有找到匹配数,ECX一定为0

下面的程序片断在字符串中查找一个字符。EDI指向目标字符串,ECX设为字符串所占的字节数。AL为要查找的字符,以字节为单位进行比较(SCASB),当发现相等时停止比较(前缀为REPNZ)。

szStr   byte    'How do you do!', 0

 

        lea     edi, szStr      ; 指向目标字符串

        mov     ecx, 15         ; szStr15个字节

        cld                     ; 地址由低至高

        mov     al, 'y'

        repnz   scasb           ; ZF=1则停止扫描

前面7个字符都不等于AL,直到比较第8个字符时,才找到匹配数,ZF=1,不再继续扫描。注意:EDI指向字符o,而不是yECX=7,表示后面还有7个字节未扫描。如图3-1所示。

 

+0

+1

+2

+3

+4

+5

+6

+7

+8

+9

+10

+11

+12

+13

+14

szStr

H

o

w

 

d

o

 

y

o

u

 

d

o

!

00H

 

 

 

 

 

 

 

 

 

EDI

 

 

 

 

 

 

3-1  在字符串中查找一个字符

在下面的程序中首先求出字符串'How do you do!'的长度。接着,再将字符串中出现的第1个字符’o’替换为’O’,采用地址从低到高的顺序扫描,扫描结束时,EDI指向字符’w’ 最后,将字符串中出现的最后1个字符’ ’替换为’*’,采用地址从高到低的顺序扫描,扫描结束时,EDI指向字符’u’。执行结果为:

HOw do you*do!

nLen = 15

;程序清单:scanchar.asm(字符扫描与替换)

.386

.model flat,stdcall

option casemap:none

includelib      msvcrt.lib

printf          PROTO C :dword,:vararg

time            PROTO C :dword

.data

szStr   byte    'How do you do!', 0

nLen    dword   0               ; 字符串长度

szLen   byte    0ah, 'nLen = %d', 0ah, 0

.code

start:

        lea     edi, szStr      ; 指向目标字符串

        mov     al, 0

        repnz   scasb           ; ZF=1则停止扫描

        sub     edi, offset szStr

        mov     nLen, edi

 

        lea     edi, szStr      ; 指向目标字符串

        mov     ecx, nLen       ; szStr15个字节

        cld                     ; 地址由低至高

        mov     al, 'o'

        repnz   scasb           ; ZF=1则停止扫描

        jnz     c10

 

        mov     byte ptr [edi-1], 'O'   ; 替换第1oO

 

c10:

 

        lea     edi, szStr      ; 指向目标字符串

        add     edi, nLen

        dec     edi             ; 指向最后1个字符'!'

        mov     ecx, nLen       ; szStr15个字节

        std                     ; 地址由低至高

        mov     al, ' '

        repnz   scasb           ; ZF=1则停止扫描

        jnz     c20

 

        mov     byte ptr [edi+1], '*'   ; 替换最后1个空格为*

 

c20:

 

        invoke  printf, offset szStr            ; 显示字符串

        invoke  printf, offset szLen, nLen      ; 显示字符串长度

        ret

end     start

3.2 内存块复制

MOVSB/W/D将操作数从一个内存单元传送到另一个内存单元。它经常和REP前缀同时使用,将一个内存块(DS:ESI指向的源数据块)复制到另一个内存块(ES:EDI指向的目标数据块)。使用MOVSB时,传送单位为字节;使用MOVSW时,传送单位为字;使用MOVSD时,传送单位为双字。

Windows环境中,DSES已由操作系统设置了,程序中不必考虑DSES的赋值。

1. 数组的复制

下面的程序将数组Array1复制给数组Array2

Array1  dword   1, 10, 100, 1000, 10000

Array2  dword   5 dup (0)

 

lea     esi, Array1

lea     edi, Array2

cld

mov     ecx, 5

rep     movsd

MOVSD每次传送一个双字,传送一次后,ESIEDI自动加4,指向下一个双字。由于有REP前缀,每次传送后,ECX自动减1。传送5次后,ECX=0时,传送结束,此时Array2Array1中的5个元素完全相等。如图3-2所示。

ESI=00402144

00000001

 

00402144

00000001

00402148

0000000A

 

00402148

0000000A

0040214C

00000064

双字传送

0040214C

00000064

00402150

000003E8

ECX=5

00402150

000003E8

00402154

00002710

DF=0

00402154

00002710

EDI=00402158

00000000

 

ESI=00402158

00000001

0040215C

00000000

 

0040215C

0000000A

00402160

00000000

 

00402160

00000064

00402164

00000000

 

00402164

000003E8

00402168

00000000

 

00402168

00002710

0040216C

 

 

EDI=0040216C

 

开始传送前的状态

 

传送完成后的状态

3-2  数据块传送(源数据块和目标数据块没有重叠)

在这个例子中,如果要使用MOVSW,按字为单位传送,则后面两条指令应修改为:

mov     ecx, 10

rep     movsw

如果要使用MOVSB,按字节为单位传送,则后面两条指令应修改为:

mov     ecx, 20

rep     movsb

按双字、字、字节都可以完成数据块的传送,应尽量使用按双字的传送方式,其效率最高。特别是源地址和目标地址应设置为4的倍数,即地址按4字节对齐时,完成数据块传送所花的时间最短。

2. 从字符串中删除一个字符

假设一个编辑软件把用户输入的一行字符存储在缓冲区InBuffer中:

InBuffer        byte    'Hellox World!', 0

这里的字符x是多余的,要把它删掉。如果用块传送指令,指令代码为:

lea     esi, InBuffer+6  ; ESI指向字符' '

lea     edi, InBuffer+5  ; EDI指向字符'x'

cld                      ; 地址由低至高

mov     ecx, 8           ; 传送8

rep     movsb            ; 以字节为单位传送

块传送之前的准备过程和传送完成后的结果如图3-3所示。

InBuffer

+0

+1

+2

+3

+4

+5

+6

+7

+8

+9

+10

+11

+12

+13

 

传送前

H

e

l

l

o

EDI

ESI

x

 

W

o

r

l

d

!

00H

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

传送1次后

H

e

l

l

o

W

EDI

ESI

W

o

r

l

d

!

00H

00H

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

传送2次后

H

e

l

l

o

W

o

EDI

ESI

o

r

l

d

!

00H

00H

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

传送8次后

H

e

l

l

o

 

W

o

r

l

d

!

00H

EDI

ESI

00H

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

3-3 数据块传送(源数据块和目标数据块有重叠,ESI>EDI)

lea  esi, InBuffer+6”也可以写做“mov  esi, offset InBuffer+6”,将InBuffer字符串的第6个字符(即x)的地址赋给ESI

从数组中删除一个元素时,同样可以使用这种传送方式。

3. 向字符串中插入一个字符

要插入一个字符到字符串中,需将插入点后面的所有字符向后移动,给这个字符留出空间。这个字符串为:

InBuffer        byte    'Hello Wrld!', 0

要把o插入字符Wr中间。需将“rld!”和0向后移动,如果用块传送指令,指令代码为:

lea     esi, InBuffer+7  ; ESI指向字符'x'

lea     edi, InBuffer+8  ; EDI指向字符' '

cld                      ; 地址由低至高

mov     ecx, 5           ; 传送5

rep     movsb            ; 以字节为单位传送

上面指令执行后,InBuffer字符串变为:

Hello Wrrrrrr

因此,这个程序是错误的。数据块的传送过程如图3-4所示。

InBuffer

+0

+1

+2

+3

+4

+5

+6

+7

+8

+9

+10

+11

+12

+13

 

传送前

H

e

l

l

o

 

W

ESI

EDI

r

l

d

!

00H

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

传送1次后

H

e

l

l

o

 

W

r

ESI

EDI

r

d

!

00H

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

传送2次后

H

e

l

l

o

 

W

r

r

ESI

EDI

r

!

00H

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

传送5次后

H

e

l

l

o

 

W

r

r

r

r

r

ESI

EDI

r

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

3-4  数据块传送(源数据块和目标数据块有重叠,DF=0ESI<EDI)

之所以发生这种状况,是因为在传送的过程中,源数据在还没有进行传送就被覆盖了。比如字符l应该向后移动一个字节,但该字符在没有传送之前就在第1次传送中被字符r覆盖了。

为避免这种问题,首先将字符串的最后一个字节00H向后移动一个字节,再依次传送!dlr。正确的传送过程如图4-4所示。

InBuffer

+0

+1

+2

+3

+4

+5

+6

+7

+8

+9

+10

+11

+12

+13

 

传送前

H

e

l

l

o

 

W

r

l

d

!

ESI

EDI

00H

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

传送1次后

H

e

l

l

o

 

W

r

l

d

ESI

EDI

!

00H

00H

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

传送2次后

H

e

l

l

o

 

W

r

l

ESI

EDI

d

!

!

00H

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

传送3次后

H

e

l

l

o

 

W

r

ESI

EDI

l

d

d

!

00H

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

传送4次后

H

e

l

l

o

 

W

ESI

EDI

r

l

l

d

!

00H

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

传送5次后

H

e

l

l

o

 

ESI

EDI

W

r

r

l

d

!

00H

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

4-4  数据块传送(源数据块和目标数据块有重叠,DF=1ESI<EDI)

传送完成后,“rld!”和0向后移动了1个字节。InBuffer+7就可以用来存放新的字符o了。

这样的传送方式,首先传送的是地址最高的源数据,即由高向低的传送方式。DF必须设置为1,每次传送后,ESIEDI自动地减少。

InBuffer        byte    'Hello Wrld!', 0, ?

lea     esi, InBuffer+11  ; ESI指向字符00H

lea     edi, InBuffer+12  ; EDI指向?所在的位置

std                       ; 地址由高至低

mov     ecx, 5            ; 传送5

rep     movsb             ; 以字节为单位传送

cld                       ; 恢复为"地址由低至高"

mov     InBuffer+7, 'o'   ; 插入字符'o'

完成传送后,用CLDDF设置为0。最后,将o存入到InBuffer+7中。

向数组中插入一个元素时,也需要使用这种方式。

4. 块传送的3种情况

根据源数据块和目标数据块是否重叠,以及数据块的地址前后顺序,将数据块的传送分为3种情况。如图4-5所示,源数据块用虚线框表示,而目标数据块用实线框表示。

(1)    源数据块和目标数据块不重叠。DF=0DF=1均可。

(2)    源数据块和目标数据块重叠,目标数据块的地址较小。只能设置DF=0ESIEDI分别执行源数据块和目标数据块的第1个单元的地址。

(3)    源数据块和目标数据块重叠,目标数据块的地址较大。只能设置DF=1ESIEDI指向源数据块和目标数据块的最后一个传送单位。如果是字节传送,指向最后一个字节;如果是字传送,指向最后一个字;如果是双字传送,指向最后一个双字。

EDI

EDI

 

 

 

 

目标数据块

 

 

 

 

 

 

 

 

目标数据块

 

源数据块

 

 

ESI

 

 

 

ESI

 

 

 

ESI

 

 

源数据块

 

源数据块

 

 

 

 

 

 

 

 

目标数据块

 

 

 

 

 

 

EDI

 

 

 

 

    ESI>EDI, DF=0

    ESI<EDI, DF=1

 

 

不重叠

                重叠

               重叠

 

4-5  块传送的3种情况

REP前缀的块传送的次数为ECX。传送完成后,ECX=0ESI指向源数据块外的下一个传送单位的地址,EDI指向目标数据块外的下一个传送地址。因为在传送完最后一个单位时,ESIEDI继续增加(DF=0)或减小(DF=1)。

下面的程序,进行了2次内存块复制操作,对应于上述的第23种情况。

;程序清单:memfunc.asm(内存块处理)

.386

.model flat,stdcall

includelib      msvcrt.lib

printf          PROTO C :dword,:vararg

.data

szFmt   byte    'Array[%2d]=%4d', 0ah, 0

Array   dword   1, 2, 4, 8, 16, 16, 32, 64, 128, 512, 1024

.code

start:

;              +0   +4   +8  +12  +16  +20  +24  +28  +32  +36  +40

;Array  dword   1,   2,   4,   8,  16,  16,  32,  64, 128, 512,1024

        ; 最后5个元素向前移动4个字节, 变为:

;Array  dword   1,   2,   4,   8,  16,  32,  64, 128, 512,1024,1024

 

        mov     edi, offset Array+20       ; EDI是目标数据块的首地址

        mov     esi, offset Array+24        ; ESI是源数据块的首地址

        mov     ecx, 20      ; 数据块的长度

        cld                     ; 地址由低至高

        rep     movsb           ; 传送数据

        cmp     edi, esi        ; 比较源地址和目标地址

        jbe     f10             ; EDI<=ESI, 由低至高传送

        std                     ; EDI>ESI, 由高至低传送

        add     esi, ecx        ;

        dec     esi             ; ESI指向源数据块的最后1个字节

        add     edi, ecx

        dec     edi             ; EDI指向目标数据块的最后1个字节

f10:   

        rep     movsb           ; 传送数据

 

;              +0   +4   +8  +12  +16  +20  +24  +28  +32  +36  +40

;Array  dword   1,   2,   4,   8,  16,  32,  64, 128, 512,1024,1024

;       ; 最后2个元素向后移动4个字节

;Array  dword   1,   2,   4,   8,  16,  32,  64, 128, 512, 512,1024

 

        mov     edi, offset Array+36       ; EDI是目标数据块的首地址

        mov     esi, offset Array+32        ; ESI是源数据块的首地址

        mov     ecx, 8      ; 数据块的长度

             std

        rep     movsb           ; 传送数据

 

;              +0   +4   +8  +12  +16  +20  +24  +28  +32  +36  +40

;Array  dword   1,   2,   4,   8,  16,  32,  64, 128, 512, 512,1024

;                                                     ^^ 替换为256

        mov     Array+32, 256

 

        lea     esi, Array      ; ESI指向数组的第1个元素

        xor     ebx, ebx        ; EBX为数组下标i

        cld                     ; 地址由低至高

f20:   

        lodsd                   ; 取出1个元素至AX, ESI4.

                                ; printf(szFmt, i, Array[i]);

        invoke  printf, offset szFmt, ebx, eax

        inc     ebx             ; 数组下标加1

        cmp     ebx, 11         ; 数组中共有11个元素, 最大下标=10

        jb      f20             ; 继续处理下一个元素

        ret

end     start

3.3 字符串插入

字符串就是特殊的数据块,以00H字符结尾。每个字符占1字节,存放的是它的ASCII码值。

下面的程序将一个字符串插入到另一个字符串的中间,两个字符串和插入位置都从键盘输入。

程序中,调用了strlen函数求出字符串的长度,在实验中可以尝试用块操作指令替换strlen函数。还可以增加对输入参数进行检查的条件,如nPos必须小于nLen1等。

;程序清单:inserts.asm(字符串插入)

.386

.model flat,stdcall

includelib      msvcrt.lib

printf          PROTO C :dword,:vararg

scanf           PROTO C :dword,:vararg

strlen          PROTO C :dword

.data

szFmt           byte    'result = "%s"', 0ah, 0

szStr1          byte    80 dup(0)               ; 1个字符串

szStr2          byte    80 dup(0)               ; 2个字符串

nLen1           dword   0                       ; 1个字符串的长度

nLen2           dword   0                       ; 2个字符串的长度

nPos            dword   0                       ; 插入位置

szStr           byte    160 dup(0)              ; 结果字符串

szInFormat      byte    '%s %s %d', 0

.code

start:

        ; 输入3个参数, szStr1, szStr2, nPos

        invoke  scanf, offset szInFormat,

                offset szStr1, offset szStr2, offset nPos

 

        ; 求第1个字符串的长度

        invoke  strlen, offset szStr1

        mov     nLen1, eax

 

        ; 求第2个字符串的长度

        invoke  strlen, offset szStr2

        mov     nLen2, eax

 

        ; 复制第1个字符串szStr1到结果字符串szStr

        lea     esi, szStr1

        lea     edi, szStr

        mov     ecx, nLen1

        cld

        rep     movsb

 

        ; szStrnPos之后的部分字符串向后移动nLen2个字节

        ; 为第2个字符串留出位置

        mov     ecx, nLen1

        sub     ecx, nPos

        lea     esi, szStr

        add     esi, nLen1

        dec     esi

        mov     edi, esi

        add     edi, nLen2

        std

        rep     movsb

 

        ; 复制第2个字符串szStr2到结果字符串szStr+nPos

        inc     esi

        mov     edi, esi

        lea     esi, szStr2

        mov     ecx, nLen2

        cld

        rep     movsb

 

        invoke  printf, offset szFmt, offset szStr

        ret

end     start

程序执行结果如下,其中第1行的内容为键盘输入,按回车键结束。

abcghijklmnopqrstuvwxyz def 3

result = "abcdefghijklmnopqrstuvwxyz"

3.4 实验题:多个字符串的排序

将多个字符串按升序排序,字符串直接定义在程序的数据区中,并将排序后的结果输出。

要求:

1.    10个字符串定义如下:

str1            byte    'Use', 0

str2            byte    'this', 0

str3            byte    'guide', 0

str4            byte    'with', 0

str5            byte    'ATADRVR', 0

str6            byte    'versions', 0

str7            byte    '14', 0

str8            byte    'and', 0

str9            byte    '15', 0

str10           byte    '.', 0

2.    数据区还需要定义哪些其他的内容,使程序易于编写。比较程序的代码长度(程序大小、源程序长度等)。

3.    如何最大程度地降低字符串的比较次数?

4.    记录程序运行时间,并考虑如何缩短程序运行时间。

 
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值