同实验10.2,不上题目了。
题目大概就是说,将保存在数据段的字型的数值,以10进制的方式显示在屏幕上。这里涉及到两点,一是打印到屏幕,即打印到内存空间B8000H~BFFFFH之间,其中每个字符占一个字,高位存放属性,低位存放字符,这段32KB的空间共可以显示25*80个字符,也就是每行80*2=160个字节。
二就是将存放在内存当中的二进制数值(以十六进制的方式显示),转换成10进制。因为显存只认识ASCII码,又因为数据在内存中是以十六进制显示的,所以我们将在数据段中的数值变为ASCII码中的十六进制。
0~9在ASCII码中的范围是30H~39H,我们要将数字中的每一位都转换成这种形式。
怎么取每一位,用书上讲的一直除10,除到商为0时,就结束。这种方法得到的每一位的顺序刚好是相反的,即12366取每一位的顺序是66321,这样我们可以用栈来解决,每取到一位,加上30H,然后进栈,最后我们要打印的时候依次取出来就行了。
到了这里还有一些问题,比如:你取位的时候可以通过判断商是否为0来结束,那么你打印的时候又怎么判断有多少位?我一开始想到的是在循环里面计数,可是难以实现,因为寄存器不够用,而我又不想用。当然你可以在栈顶(SS:[0])中来计数,每打印完一个数,就清零,这种方法我还没有去实现,还有另外一种方法就是,根据SP寄存器的相对偏移来确定到底有几位,我用的就是这种方法。
然后还有,如果数据段的数不止一个数,那么怎么判断后面是否还有数?这个我们可以根据0来判断,我们可以在数据段的最后加一个0,然后每次读其中的数的时候,把这个数的值给CX,用JCXZ来结束程序。
下面放代码
DATAS SEGMENT
dw 123,12366,1,8,3,38,9;此处输入数据段代码
DATAS ENDS
STACKS SEGMENT
dw 200 dup(0);此处输入堆栈段代码
STACKS ENDS
CODES SEGMENT
ASSUME CS:CODES,DS:DATAS,SS:STACKS
START:
MOV AX,DATAS
MOV DS,AX
MOV AX,0B800H
MOV ES,AX
MOV DI,0
MOV SI,0
MOV AX,STACKS
MOV SS,AX
MOV SP,801
MOV DH,8 ;行
MOV DL,3 ;列
MOV CL,2 ;颜色
sub dl,1
MOV AL,160
MUL DH
MOV DI,AX ;计算行偏移
MOV AX,2
MUL DL
ADD DI,AX ;行+列偏移 保存在DI
CALL dtoc
MOV AX,4C00H
INT 21H
show_str:
MOV CL,2 ;颜色
push cx ;颜色进栈
MOV CX,801
SUB CX,SP
MOV AX,CX
MOV Bl,2
DIV Bl
SUB AX,2
MOV CX,AX
;计算CX的值
p:
POP BX ;颜色出栈
POP DX
MOV ES:[DI],DL
MOV ES:[DI+1],bl
add DI,2
PUSH BX
LOOP p
POP BX
add SI,2
jmp short dtoc
dtoc:
MOV AX,DS:[SI]
MOV CX,AX
JCXZ return
s:
MOV CL,AL
MOV CH,0
JCXZ ok ;商为0时打印
MOV DX,0
MOV BX,10
DIV BX ;商在AX 余数在DX
MOV BX, 30H
ADD DX,BX
PUSH DX
jmp short s
ok:
jmp short show_str
return:
ret
CODES ENDS
END START
做题目的时候差点被坑,因为这里12366/10,也就是 16位/8位,商保存在AL,余数保存在AH,然后就会发生溢出错误,因为AL保存不了这么大的值,这里只能用32位/16位来解决。在打印的计算CX那里,用SP最开始的值-当前的SP的值,再除2,因栈是字为单位的,一个字等于两个字节,然后在-2,为什么要-2呢?
因为在CODES段中 CALL了 dtoc ,这时候CPU会把CALL的下一个地址进栈,然后在show_str中把颜色进栈了,所以我们要减去2,就能得到正确的CX结果了。还有在p段结束后,要记得POP BX,因为最后一个循环PUSH了一个值,如果不将其出栈,那么CALL dtoc的下一条指令就不会执行,程序就不能正常结束。
还有一点 就是关于列的,为什么要-1 。因为第三列(现实生活的计数,从1开始)就是里面的第二列,00 01 02 03 04 05,我们要定位到04这个内存单元,我们可以(3-1)*2,等于4。
以后想到再补充了,欢迎大家一起学习交流。