[课题2.7]汇编入门小程序联系6

课题要求:编写3个小程序

1.  把AX中存入的16位二进制数看作是8个二进制的“四分之一字节“。试编写一个程序,要求数一个值为3(即11B)的四分之一字节数。
2.  设有一段英文,其字符变量名为MEM,并以$字符结束。试编写一个程序,查找单词MOON在该文本中的出现次数。
3. 有一个首地址为MEM的100D字数组,试编写程序删除数组中所有为0的项,并将后续项向前压缩,最后将数组的剩余部分补上0。


因程序较小,以下程序的子程序的参数全部使用了全局变量,操作起来非常方便,但也有可能会引出一些问题,不过我意在解决具体的问题,所以我最终还是决定了使用全局变量作为子程序的参数。另外我对函数参数的进栈、出栈还不太熟悉,因此子程序写的不是很标准.


代码:
assume cs:code,ds:data,ss:stack

data segment
 c dw 0            ;计数器,存储X中11B的个数
 x     dw 0ccff   ;待计算11B的个数的参数
data ends

stack segment
 dw 64 dup(0)
stack ends

code segment
start:
    mov ax,data
    mov ds,ax   ;初始化数据段
    
    mov ax,stack    ;初始化堆栈段
    mov ss,ax
    mov sp,128
     
    call Count         ;调用子程序计算11B的个数
    
    mov ax,4c00h   ;返回
    int 21h


Count proc near   
    push ax    ;保护在子程序中要使用的寄存器
    push bx
    push cx
    
    mov c,0    ;初始化计数器为0
    mov cx,8    ;设置循环次数为8,因一个字单元可分为8个“四分之一字节”
    mov bx,c000h  ;0c00h=1100 0000 0000 0000b,即从左到右始每次测试二位,直到bx移至为0
s:
    mov ax,x          ;(AX)=X
    and ax,bx         ;测试AX中的某二位是否为11B
    cmp ax,bx   ;比较是否为11B
    jz counter   ;如果为11B,跳转到counter处使计数器C加1
    jmp s1       ;比较不为11B,直接跳过inc c
counter:
    inc c
s1:          
    push cx             ;由于使用cl作移动位数寄存器,因此先保存一下
    mov cl,2    ;设置向右移动两位
    shr bx,cl      ;(BX)向右移动两位,即如果(BX)=1100 0000 0000 0000B,则移动后为(BX)=0011 0000 0000 0000B
    pop cx     ;恢复CX内容
  
    loop s                ;如果(CX)=(CX)-1不为0,则返回开始循环处
  
    pop cx     ;恢复各个寄存器
    pop bx
    pop ax
    
    ret                     ;返回函数调用处,严格来说这个算不上是真正的函数,但为了好听点,就叫它函数吧
Count endp

code ends
end start



代码:
assume cs:code,ds:data,ss:stack

data segment
 c  dw 0                                        ;计数器,存储字符串中“MOON”的个数
 str db 'MOONMOONMOONF','$'    ;待求字符串,传说中的数组,不过这里是以'$'结束的
data ends

stack segment
 dw 16 dup(0)                 
stack ends

code segment
start:
   mov ax,data                  ;初始化数据段和堆栈
   mov ds,ax

   mov ax,stack
   mov ss,ax

   mov cx,0                        ;初始化计数器为0
   call Count        ;调用子程序计算字符串中“MOON”的个数,结果存储在C中

   mov ah,4ch       ;返回
   int 21h
  
Count proc near
    push si                          ;保护一下SI的内容,因下面要使用到它

    mov si,0                        ;初始化数组下标SI为0
s2:
    cmp byte  ptr str[si],'$'       ;以下连续比较四个字符中是否有'$',因为后面0~4个字符之中如果有'$',则不必再检测是为为四个字符为“MOON”,经我反汇编查看了一下WIN-TC输出的汇编源码,我发现我写的这个检测方法要比它编译的稍微好一点点.如果没检测到’$',则继续往下检测这四个字符是否为“MOON”,否则跳到S3处结束循环
    jz s3
    cmp byte ptr str[si+1],'$'
    jz s3
    cmp byte ptr str[si+2],'$'
    jz s3
    cmp byte ptr str[si+3],'$'
    jz s3

    cmp byte ptr str[si],'M'   ;比较字符串str中是当前下标指向的及后面的字符是否为“MOON”,如果Y,则继续往下执行,否则返回S2处执行
    jnz s2
    cmp byte ptr str[si+1],'O'
    jnz s2
    cmp byte ptr str[si+2],'O'
    jnz s2
    cmp byte ptr str[si+3],'N'
    jnz s2

    inc c             ;如果运行到这里,则说明在str中检测到了一个"MOON"
    add si,4                ;(SI)=(SI)+4,指针指向字符串后四个字符的开始处
    jmp s2                                   ;进入下一轮检测

s3:
   pop si                                      ;恢复SI
    
    ret                                          ;返回函数调用处
Count endp

code ends
end start





这个程由于我使用了优化后的算法,因此算法的时间复杂度由原来的o(n^2)变为了o(n)。大概思想如下:
代码:
void DeleteAllZero(int iarray[],int len)
{
   int i,p;
   for(i=p=0;i<len-1;i++){
       if(iarray[i]!=0){
           p++;
       }
        else{
           if(iarray[i+1]!=0){
               iarray[p]=iarray[i+1];
               iarray[i+1]=0;
               p++;
            }
        }
   }

}

只需要存当前扫描到的第一个0的位置p即可。每次扫到一个不为0的数就移到p,并将p++;扫到一个0的话p不变,继续扫下一个数。




以下为完整的程序,可以在程序中看到上面优化后的循环的影子。

代码:
assume cs:code,ds:data,ss:stack

data segment
iarray dw 1,0,2,0,3,0,4,0,5,0            ;100个字型元素,好多啊,为了方便,我使用了小且顺序规律的元素
        dw 1,0,2,0,3,0,4,0,5,0
        dw 1,0,2,0,3,0,4,0,5,0
        dw 1,0,2,0,3,0,4,0,5,0
        dw 1,0,2,0,3,0,4,0,5,0

        dw 1,0,2,0,3,0,4,0,5,0
        dw 1,0,2,0,3,0,4,0,5,0
        dw 1,0,2,0,3,0,4,0,5,0
        dw 1,0,2,0,3,0,4,0,5,0
        dw 1,0,2,0,3,0,4,0,5,0

len dw 100                                          ;数组长度
data ends

stack segment
 dw 128 dup(0)
stack ends

code segment
start:
   mov ax,data                            ;初始化各段寄存器
   mov ds,ax

   mov ax,stack
   mov ss,ax
   mov sp,256

   call DelAllZero                           ;调用子程序对数组进行“删除”和”压缩“处理

   mov ah,4ch
   int 21h 

DelAllZero proc near
    push ax                                    ;保护各个使用到的寄存器
    push bx
    push si
    push di
  
    mov si,0                                    ;SI存储当前数组中第一个为0的位置,这里的第一个并不仅仅指数组开始时的,它还指删除一个或几个0后的数组时的第一个为0的位置
    mov di,0                                    ;DI用来遍历数组中的每一个元素
    mov ax, len                                ;AX表示数组的长度,这个函数有两个数组:数组首地址和数组长度
    add ax,len                                  ;(AX)=(AX)+LEN,之所以要再加一次LEN,因为数组元素为字单元,每次下标SI要加2才能取下一个元素,因为下标SI和AX的比较结果为循环退出的条件。为了方便,决定要将AX内容加倍
    sub ax,2                                     ;AX内容减2,表示SI只要扫描到数组倒数第二个元素即可
s4:
    cmp si,ax            ;(SI)>=(AX)时表示检测完毕,应该退出循环
    jnc exit1                                      ;不再满足条件时退出循环
    cmp word ptr iarray[si],0            ;检测SI指向的元素是否为0,如果为0则SI加2检测下一个元素
    jz s5

    add si,2                                      ;SI加2指向下一个元素
    add di,2                                      ;DI加2指向下一个元素,直到指向首个为0的元素
    jmp s4
s5:
    add si,2             ;移动SI检测下一个元素
    cmp word ptr iarray[si],0     ;如果非0,则将该元素移至DI指向的位置,覆盖前面为0的元素
    jz s4                ;为0,返回继续检测

    mov bx,iarray[si]          ;将SI指向的元素移至DI指向的位置,即覆盖前面的0
    mov iarray[di],bx         
    mov word ptr iarray[si],0      ;移动之后将SI指向的元素清除为0
    add di,2                                         ;DI加2为指向被覆盖后的元素的后一个位置
    jmp s4               ;返回继续检测

exit1:
    pop di                                             ;恢复各个寄存器
    pop si
    pop bx
    pop ax
    ret                 ;返回
DelAllZero endp

code ends
end start
总结:好久没做课题了,这两天终于做了两个课题。虽然仅仅是一点点小成就而已,但还是蛮开心的。

  • 标 题:答复
  • 作 者:没有风
  • 时 间:2008-03-12 22:34:02

一二张图片为程序1的运行结果,三四五为程序2的程序结果,六为程序3的运行结果。
一为加载程序到内存中的源码,二为运行后的,其中00006H为C,即11B个数,CCFFH为X。
三为代码,四为数据段的100个字单元,以及0064H为数组长度;五为删除、压缩后的结果,只是有点模糊。
六为运行后的结果,可以看出(X)=0003H str="MOONMOONMOONF$',前面的03为str中“MOON”的个数。