汇编语言(王爽第三版)实验9 根据材料编程
实验9 根据材料编程
编程:
在屏幕中间分别显示绿色、绿底红色、白底蓝色的字符串:“welcome to masm!”
程序分析:
1)在内存中定义并初始化一个字符串:“welcome to masm!”(这个在数据段中定义就可以,使用db)
2)由材料提示(这里是8位的二进制的组合,形成一个属性)
属性字节的格式:
7 6 5 4 3 2 1 0
BL(闪烁) R(背景) G(背景) B(背景) I(高亮) R(前景) G(前景) B(前景)
知道: 绿色属性:00000010B==02H
绿底红色:00100100B==24H
白底蓝色:01110001B==71H
也就是说02H如果存储在显示缓冲区中的奇数内存单元时,它显示的字符是绿色的。
3)在命令提示符窗口或dos窗口,我们可以显示80X25的字符(我的机器行数多,命令提示符窗口,跟设置有关)。每行80个字符,一共是25行。它们在内存中是在一个内存段中存储的,这个内存区域叫做显示缓冲区。从物理地址B8000H~BFFFH这个32K的内存区域就是显示缓冲区。
我们可以使用debug查看下,例如段地址是B800H,偏移地址是:0000H;
d B800:0000
我们可以在这里实验下,在当前屏幕下,使用e命令修改B800:0000H开始的内存,看看有什么反应?
ABC0:0030 20 07 20 07 20 07 20 07-20 07 20 07 20 07 20 07 . . . . . . . .
B800:0040 41 07 58 07 2C 07 34 07-43 07 30 07 30 07 20 07 A.X.,.4.C.0.0. .
B800:0050 20 07 20 07 20 07 20 07-20 07 20 07 20 07 20 07 . . . . . . . .
B800:0060 20 07 20 07 20 07 20 07-20 07 20 07 20 07 20 07 . . . . . . . .
B800:0070 20 07 20 07 20 07 20 07-20 07 20 07 20 07 20 07 . . . . . . . .
此处省略内存的显示。
-e b800:0000
B800:0000 42.41 07.02 38.42 07.02 30.43 07.02 30.
我们发现,在B800:0000内存开始处写入数值,例如41H,0001单元:02H,在屏幕的第一行立即就出现了一个绿色的A(再换行就没了)。也就是说从B800:0000H开始,偶数地址是显示的字符,奇数地址是负责前面字符的显示属性的。也就是说在显示缓冲区中,每2个字节负责屏幕上一个字符的显示(包括显示的属性)。
在显示缓冲区内写入的字符,立即就显示在屏幕上。
因为每行要显示80个字符,故从0000H~009FH是显示的第一行(共160个字节)。每行可以类推。
题目中由于需要让此字符串(共三行,显示在屏幕中间),故行数应该是在12、13、14行。那么它们的偏移地址的首地址是多少?0780H、0820H、08C0H。(粗略计算,实际上屏幕是多少行也不完全固定的。此处我们不重新计算段地址了,段地址还是B800H,只计算偏移地址。)
“welcome to masm!”字符串有19个字符,也就是说它在显示内存区域中所占空间为38个字节。每行有80个字符显示,那么它在中间显示的话,取中显示,应该在0-159字节中间的第61-99字节(3C~63H);也就是在列上偏移3C(开始)
行和列的偏移地址确定了。我们就可以试着输出第一行,它的偏移地址是:0780H+003
CH=7BCH(1980)。我们使用debug在B800:7BCH处写内存,发现实验成功了。依次类推。
其它剩余2行的偏移地址,我们按照159-31=128计算,即(bx)=(bx)+128.
字符串和字符的属性是存储在内存的data段中的,如何从内存中将它们(每个字节)取出,然后通过寄存器中转,然后写入到指定的显示缓冲区内(也是内存单元),并指定每个字符的属性(在奇数位置)。
(es)= (data);(ds)= 0B800H,中转寄存器为ax(按要求将它们分开为2个8位寄存器)。
由于在屏幕上输出3行“welcome to masm!”,故外循环3次来输出3行,每次一行输出。此为外循环。使用栈来存储CX的状态。
内循环:将字符和字符属性按照顺序写入到显示缓冲区内。
源代码如下:
assume cs:codesg
data segment
db 'welcome to masm!'
db 02H,24H,71H ;字符显示的属性值
data ends
stack segment
db 10 dup(0)
stack ends
codesg segment
start: ;初始化data数据段,es:di指向data
mov ax,data
mov es,ax
mov di,0
;初始化显示缓冲区,ds:bx指向显示缓冲区。
mov ax,0b800H
mov ds,ax
;25行取中是12、13、14行 ,80列取中开始是61列
;12行的偏移量是12*16=1920
;总偏移量为(偏移地址)1920+60=1980?
mov bx,1980
;字符的属性在数据段中的偏移量
mov si,16
;建栈,并初始化栈顶,熟悉栈结构。
;其实这里都不用人工建栈,有系统自动的。
mov ax,stack
mov ss,ax
mov sp,0
mov cx,3 ;计数器初始化为3(循环显示3次)
s: push cx ;入栈保护CX,在stack中
mov cx,16 ;内循环为16次,16个字符
output: ;将字符写入显存中
mov al,es:[di]
mov [bx],al
;将字符属性写入显存中
mov ah,es:[si]
mov [bx+1],ah
inc di
add bx,2
loop output
;每行输出的偏移量为128字节
add bx,128
mov di,0
inc si
pop cx ;出栈恢复cx计数器值
loop s
mov ax,4c00H
int 21H
codesg ends
end start
总结:
1. 合理利用栈结构保存寄存器变量的值。
2. 熟练掌握[bx+idata]这种CPU寻址的方式。
3. 在显存中,甚至是内存中,它们都是线性存储的,以列的形式存储的。不存在行的概念的,只不过在计算机屏幕上,还有debug中有行的概念,为了显示方便。