[课题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