实验9
编程,在屏幕中间分别显示绿色,绿底红色,白底蓝色的字符串‘welcome to masm!’。
80x25彩色字符模式下的显示缓冲区,每页可以显示25行,每行80个字符,其中每个字符占两个字节的存储空间,低位字节存储字符的ASCII值,高位字节存储字符的属性。所以一行总共有160个字节。其中属性字节的格式如下:
7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | |
---|---|---|---|---|---|---|---|---|
含义 | BL | R | G | B | I | R | G | B |
其中BL为闪烁,R为红色,G为绿色,B为蓝色,I为高亮,在高位的RGB表示背景颜色,低位的表示前景颜色。
以绿底红色为例,它的颜色属性为00100100B。显示缓冲区的段地址为0b800h,默认在第0页的屏幕中间的偏移地址为12x160+(40-16/2)x2。其中16为本程序中字符串的字节数。搞清楚了目的地址和颜色属性之后,我们开始编写程序:
assume cs:code
data segment
db 'welcome to masm!'
data ends
code segment
start:mov ax,0b800h
mov es,ax
mov di,12*160+32*2 目的地址存储在es:di中
mov ax,data
mov ds,ax
mov bx,0
mov cx,10h
s: mov al,[bx]
mov es:[di],al
mov byte ptr es:[di+1],00100100B
inc bx
add di,2
loop s
mov ax,4c00h
int 21h
code ends
end start
实验10
问题1 显示字符串
编写一个通用的子程序来让调用者可以决定显示的位置(行、列、内容和颜色)
子程序描述如下:
名称:show_str
功能:在指定的位置、用特定的颜色,显示一个用0结束的字符串
参数:(dh)=行号(取值范围为0-24),(dl)=列号(取值范围为0-79),(cl)=颜色,ds:si指向字符串的首地址
返回:无
在屏幕的8行3列,用绿色显示data段中的字符串,完整的程序如下:
assume cs:code
data segment
db 'Welcome to masm!',0
data ends
code segment
start: mov dh,8
mov dl,3
mov cl,2
mov ax,data
mov ds,ax
mov si,0
call show_str
mov ax,4c00h
int 21h
show_str: push cx
push dx
push ax
push si
push ds
push di
push es
mov ax,0b800h
mov es,ax
mov di,dh*160+dl*2 es:di为目的地址
s: mov al,[si]
cmp al,0
je done
mov es:[di],al
mov es:[di+1],cl
add di,2
inc si
jmp short s
done: pop es
pop di
pop ds
pop si
pop ax
pop dx
pop cs
ret
code ends
end start
问题2 解决除法溢出
问题:除法溢出指的是商过大,超出了寄存器的存储范围。比如:16位的被除数和8位的除数在做除法的时候,用al存储结果的商,ah存储结果的余数。如果当除数为1时,那么商也是一个16位数据,这超过了al这个8位寄存器所能表示数据的范围了。
子程序描述如下:
名称:divdw
功能:进行不会溢出的除法运算,被除数为dword型,除数为word型,结果为dword型。
参数:(ax)=dword型数据的低16位,(dx)=dword型数据的高16位,(cx)=除数
返回:(dx)=结果的高16位,(ax)=结果的低16位,(cx)=余数
提示:参考公式如下:
X:被除数,范围:[0,FFFFFFFF]
N:除数,范围:[0,FFFF]
H:X高16位,范围:[0,FFFF]
L:X低16位,范围:[0,FFFF]
int():描述性运算符,取商,比如,int(38/10)=3
rem():描述性运算符,取余数,比如,rem(38/10)=8
公式:X/N=int(H/N)*65536+[rem(H/N)*65536+L]/N
注意:乘以65536相当于左移16位。
计算1000000/10(F4240H/0AH)完整程序如下:
assume cs:code
data segment
dw 4 dup(0)
data ends
code segment
start: mov ax,data
mov ds,ax
mov si,0
mov ax,4240h
mov dx,0fh
mov cx,0ah
call divdw
mov ax,4c00h
int 21h
divdw: push ds
push si
push cx
push ax
push dx
mov [si],ax 保存被除数的低16位L
mov [si+2],dx 保存被除数的高16位H
mov ax,dx
mov dx,0
div cx 用高16位H除以除数N
mov [si+4],ax 保存商
mov ax,[si]
div cx rem(H/N)作为高16位,L作为低16位,除以N
mov [si+2],ax
mov [si],dx
//将商的高16位存储在[si+4]处,低16位存储在[si+2]处,余数存储在[si]处。
done: pop dx
pop ax
pop cx
mov cx,[si] 保存返回值到相应的寄存器中
mov ax,[si+2]
mov dx,[si+4]
pop si
pop ds
ret
code ends
end start
问题3 数值(int)到字符串形式的转换
问题:把数据用十进制的形式显示到屏幕上,需进行两步的操作:(1),将用二进制信息存储的数据转变为十进制形式的字符串(2),显示十进制形式的字符串(只需调用问题1的show_str即可)
子程序描述如下:
名称:dtoc
功能:将word型数据转变为十进制数的字符串,字符串以0为结尾符。
参数:(ax)=word型数据、ds:si指向字符串的首地址
返回:无
编程,将数据12666以十进制形式在屏幕的8行3列,用绿色显示出来。在显示时调用本次实验中的第一个子程序show_str。
思路:想要得到十进制数12666的每一位数值,需将12666除以10,它的余数6为个位上的数值,它的商1266作为新的被除数再除以10,余数6为原数据十位上的数值,它的商再次除以10,以此类推,进行5次除以10操作就可以得到每一位的值了。
在已知数据为12666的情况下,循环次数为5,但对于数据未知的情况下,就需要判定每次得到的商是否为0了,当除到商为0时,所有位上的值就被全部求出。在此采用jcxz指令来实现此功能。
除法功能在此采用32位除以16位的方式来实现,因为这样不会导致溢出。因为如果采用16位除以8位的方式的话,那么它的商也是用8位来保存的,而12666/10=1266,这已经大于8位数据的最大值255了。
assume cs:code
data segment
db 10 dup(0)
data ends
code segment
start: mov ax,12666
mov bx,data
mov ds,bx
mov si,0
call dtoc
mov dh,8
mov dl,3
mov cl,2
call show_str
mov ax,4c00h
int 21h
dtoc: push bx
push cx
push ax
push dx
mov bx,10 整数10作为16位的除数放在bx中
mov si,9
mov byte ptr [si],0 字符串末尾置0
sub si,1
s: mov dx,0
div bx
add dl,'0' 容易看出dh中全为0,计算出数据的实际ASCII值
mov [si],dl 字节单元数据的传送,将余数保存到内存中
mov cx,ax
jcxz done
sub si,1
jmp short s
done: pop dx
pop ax
pop cx
pop bx
ret
code ends
end start
课程设计1
将实验七中的公司21年的数据在屏幕上显示出来,一共占据屏幕21行,每一行中显示出每一年的数据,依次为年份、收入、雇员数和平均收入。
思路:
先计算出每一字段的行号和列号,可得,行号范围为[2,22];每一行第一个字段起始地址为6,第二个字段起始地址为28,第三个字段起始地址为50,最后一个字段起始地址为72。这样就可以将所有数据内容显示在屏幕中央。
说明一点,前面已经实现了的子程序在此将被直接调用。
但还需要编写一个将dword型数据转换为字符串的子程序。说明如下:
名称:ddtoc(为了和word型数据转换子程序dtoc区分开来)
功能:将dword型数据转变为表示十进制数的字符串,字符串以0为结尾符
参数:(ax)=dword型数据的低16位,(dx)=dword型数据的高16位,ds:si指向字符串的首地址
返回:无
仍需要注意除法溢出的问题。
完整程序如下:
assume cs:code
data segament
db '1975','1976','1977','1978','1979','1980','1981','1982','1983'
db '1984','1985','1986','1987','1988','1989','1990','1991','1992'
db '1993','1994','1995'
//用21个字符串来表示年份
dd 16,22,382,1356,2390,8000,16000,24486,50065,97479,140417,197514
dd 245980,590827,803530,1183000,1843000,2759000,3753000,4649000,5937000
//用21个双字型数据来表示公司每年的总收入
dw 3,7,9,13,28,38,130,220,476,778,100,1442,2258,2793,4037,5635,8226
dw 11542,14430,15257,17800
//用21个字型数据来表示公司每年的员工数量
data ends
buf segment
db 16 dup(0) 这块内存是用来进行双字型到字符串转换的
buf ends
code segment
start:mov ax,data
mov ds,ax
mov bx,0 ds:bx表示数据段起始的偏移地址
mov ax,0b800h
mov es,ax
mov di,160*2 es:di指向显示缓冲区第二行第0列
mov al,21
mov dl,8
mul dl
mov si,ax 计算21*8,保存到si中,这是雇员数在数据段的起始偏移地址
mov cx,21 外层循环次数21次
body: mov ax,[bx]
mov es:[di+6],ax
mov ax,[bx+2]
mov es:[di+8],ax 年份
push si
push ds
mov ax,[bx+84]
mov dx,[bx+86]
call ddtoc
mov bp,0
subb: mov al,[si]
cmp al,0
je next
mov es:[di+bp+28],al
inc bp
inc si
jmp short subb 总收入
next: pop ds
pop si
mov ax,[si]
push ds
push si
mov ax,buf
mov ds,ax
call dtoc
mov bp,0
subbo: mov al,[si]
cmp al,0
je nnext
mov es:[di+bp+50],al
inc bp
inc si
jmp short subbo 雇员数
nnext: pop si
pop ds
mov dx,[bx+86]
mov ax,[bx+84]
div word ptr [si] 计算人均收入
push ds
push si
mov ax,buf
mov ds,ax
call dtoc 字型数据转换为字符串
mov bp,0
subbody:mov al,[si]
cmp al,0
je nnnext
mov es:[di+bp+72],al
inc bp
inc si
jmp short subbody
nnnext: pop si
pop ds
add di,160
add si,2
add bx,4
loop body
mov ax,4c00h
int 21h
ddtoc: push cx
push ax
push dx
mov ax,buf
mov ds,ax
mov si,15
mov byte ptr [si],0
dec si
s: mov cx,10 除数10保存在cx中
push si
mov si,0 将ds:si开始后的6个字节作为divdw中的临时缓存
call divdw divdw采用本文中实现的子程序
pop si
add cl,'0'
mov [si],cl
dec si
cmp dx,0
jne s
cmp ax,0
je done
jmp short s
done: inc si 注意,最后退出时si并不是刚好指向字符串首地址
pop dx
pop ax
pop cx
ret
code ends
end start