数据标号: 可以使用一种标号, 不但表示内存单元的地址, 还表示了内存单元的长度, 即表示在此标号处的单元是字节单元
,字单元
还是双字单元
以下程序将code段中a标号处的8个数据累加, 结果存储到b标号处的字中.
assume cs:code
code segment
a db 1,2,3,4,5,6,7,8
b dw 0
start: mov si,0
mov cx,8
s: mov al,a[si]
mov ah,0
add b,ax
inc si
loop s
mov ax,4c00h
int 21h
code ends
end start
在code段中的标号a,b
后面没有:
. 它们同时描述内存地址和单元长度(如 b dw 0
).
以下指令中,标号b代表了一个内存单元, 地址为code:8, 长度为2个字节
指令: mov ax, b
相当于: mov ax,cs:[8]
指令: mov b, 2
相当于: mov word ptr cs:[8],2
指令: inc b
相当于: inc word ptr cs:[8]
mov al,b
会引起编译错误, 因为b代表的内存单元是字单元, 而al是8位寄存器.
一般情况下不会在代码段中定义数据, 而是将数据定义到其他段中.
注意: 在后面加有:
的地址标号, 只能在代码段中使用, 不能在其他段中使用
assume cs:code, ds:data ;用ds寄存器和data段相连, code段中就可以使用data段中的数据标号
data segment
a db 1,2,3,4,5,6,7,8
b dw 0
data ends
code segment
start: mov ax,data
mov ds,ax
mov si,0
mov cx,8
s: mov al,a[si]
mov ah,0
add b,ax
loop s
mov ax,4c00h
int 21h
code ends
end start
注意: 如果想在代码段中直接用数据标号访问数据, 则需要用伪指令assume将标号所在的段和一个段寄存器联系起来. 否则会编译出错.
指令: mov al,a[si]
编译为: mov al,[si+0]
指令: add b,ax
编译为: add [8],ax
这些实际编译出的指令, 都默认访问单元的段地址在ds中, 而实际要访问的段为data, 所以在这些指令执行之前, ds中必须为data段的段地址.
可以将标号当做数据来定义, 此时编译器将标号所标识的地址当做数据的值
data segment
a db 1,2,3,4,5,6,7,8
b dw 0
c dw a, b
data ends
上面数据标号c处存储的两个字节型数据为标号a,b的偏移地址, 相当于:
data segment
a db 1,2,3,4,5,6,7,8
b dw 0
c dw offset a, offset b
data ends
比如:
data segment
a db 1,2,3,4,5,6,7,8
b dw 0
c dd a,b ;数据标号c处存储2个双字节型数据为标号a的偏移地址和段地址. 标号b的偏移地址和段地址
;相当于 c dw offset a, seg a, offset b, seg b ;seg操作符, 功能为取得某一标号的段地址
data ends
编程, 以16进制的形式在屏幕中间显示给定的字节型数据
分析:
- 一个字节需要用2个16进制的数码来表示. 所以子程序需要在屏幕上显示2个ASCII字符
- 可以将一个字节的高4位和低4位分开, 分别用它们的值得到对应的数码字符
0~9
:数值+30h
=对应字符的ASCII码;10~15
:数值+37h
=对应字符的ASCII码- 更简便的方法, 需要找到
0~f
和0~15
之间一致的映射关系. 可以建立一张表, 依次存储字符0~f
, 可以通过数值0~15
直接查找到对应的字符
子程序如下:
showbyte: jmp short show
table db '0123456789ABCDEF' ;字符表
show: push bx
push es
push cx
mov ah,al
mov cl,4
shr ah,cl ;右移4位, ah中得到高4位的值
and al,00001111b ;al中得到低4位的值
mov bl,ah
mov bh,0
mov ah,table[bx] ;用高4位的值作为相对于table的偏移,取得对应的字符
mov bx,0b800h
mov es,bx
mov es:[160*12+40*2],ah
mov bl,al
mov bh,0
mov al,table[bx] ;用低4位的值作为相对于table的偏移,取得对应的字符
mov es:[160*12+40*2+2],ah
pop cx
pop es
pop bx
ret
在子程序中,数值015和字符0f向对应: 以数值N为table表中的偏移,可以找到对应的字符
- 为了算法的清晰和简洁
- 为了加快运算速度
- 为了使程序易于扩充
编写一个子程序, 计算sin(x), x∈{0°,30°,60°,90°,120°,150°,180°}
分析:将所要计算的sin(x)的结果都存储到一张表中; 然后用角度值来查表, 找到对应的sin(x)的值
用ax向子程序传递角度, 程序如下
showsin: jmp short show
table dw ag0,ag30,ag60,ag90,ag120,ag150,ag180 ;字符串偏移地址表
ag0 db '0',0 ;sin(0)对应的字符串"0"
ag30 db '0.5',0 ;sin(30)对应的字符串"0.5"
ag60 db '0.866',0 ;sin(60)对应的字符串"0.866"
ag90 db '1',0 ;sin(90)对应的字符串"1"
ag120 db '0.866',0 ;sin(120)对应的字符串"0.866"
ag150 db '0.5',0 ;sin(150)对应的字符串"0.5"
ag180 db '0',0 ;sin(180)对应的字符串"0"
show: push bx
push es
push si
mov bx,0b800h
mov es,bx
;以下用角度值/30 作为相对于table的偏移,取得对应的字符串的偏移地址,放在bx中
mov ah,0
mov bl,30
div bl
mov bl,al
mov bh,0
add bx,bx ;table中存放的是字型数据, 所以bx*2
mov bx,table[bx]
;以下显示sin(x)对应的字符串
mov si,160*12+40*2
shows: mov ah,cs:[bx]
cmp ah,0
je showret
mov es:[si],ah
inc bx
add si,2
jmp short shows
showret: pop si
pop es
pop bx
ret
角度值/30=table中的偏移,可以找到对应的字符串的首地址
可以在直接定址表中存储子程序的地址, 从而方便地实现不同子程序的调用.
实现一个子程序setscreen,为显示输出提供如下功能:
- 清屏
- 设置前景色
- 设置背景色
- 向上滚动一行 入口参数说明:
- 用ah寄存器传递功能号: 0表示清屏, 1表示设置前景色, 2表示设置背景色, 3表示向上滚动一行
- 对与1,2号功能, 用al传送颜色值. (al)∈{0,1,2,3,4,5,6,7}
- 清屏: 将现场中低昂去屏幕中的字符设置为空格符
- 前景色: 设置显存中当前屏幕处于奇地址的属性字节的第0,1,2位
- 背景色: 设置显存中当前屏幕处于奇地址的属性字节的第4,5,6位
- 向上滚动一行: 依次将第n+1行的内容复制到第n行处, 最后一行为空
sub1: push bx
push cx
push es
mov bx,0b800h
mov es,bx
mov bx,0
mov cx,2000
sub1s: mov byte ptr es:[bx], ' '
add bx,2
loop sub1s
pop es
pop cx
pop bx
ret
sub2: push bx
push cx
push es
mov bx,0b800h
mov es,bx
mov bx,1
mov cx,2000
sub2s: and byte ptr es:[bx], 11111000b
or es:[bx],al
add bx,2
loop sub2s
pop es
pop cx
pop bx
ret
sub3: push bx
push cx
push es
mov cl,4
shl al,cl
mov bx,0b800h
mov es,bx
mov bx,1
mov cx,2000
sub3s: and byte ptr es:[bx],10001111b
or es:[bx],al
add bx,2
loop sub3s
pop es
pop cx
pop bx
ret
sub4: push cx
push si
push di
push es
push ds
mov si,0b800h
mov es,si
mov ds,si
mov si,160 ;ds:si指向第n+1行
mov di,0 ;es:di指向第n行
cld ;设置传输方向为正
mov cx,24 ;共复制24行
sub4s: push cx
mov cx,160 ;每行有160个字节
rep movsb ;复制
pop cx
loop sub4s
mov cx,80
mov si,0
sub4s1:mov byte ptr [160*24+si], ' ' ;最后一行清空
add si,2
loop sub4s1
pop ds
pop es
pop di
pop cx
ret
将这些功能子程序的入口地址存储在一个表中. 对应关系: 功能号*2=对应的功能子程序在地址表中的偏移
setscreen: jmp short set
table dw sub1,sub2,sub3,sub4
set: push bx
cmp ah,3
ja sret ;大于3就退出
mov bl,ah
mov bh,0
add bx,bx ;根据ah中的功能号计算对应子程序在table表中的偏移
call word prt table[bx] ;调用对应的子程序
sret: pop bx
ret
安装一个新的int7ch中断例程,为显示输出提供如下功能:
- 清屏
- 设置前景色
- 设置背景色
- 向上滚动一行
入口参数说明:
- 用ah寄存器传递功能号: 0表示清屏, 1表示设置前景色, 2表示设置背景色, 3表示向上滚动一行
- 对与1,2号功能, 用al传送颜色值. (al)∈{0,1,2,3,4,5,6,7}
分析:
- 复制代码到0:200
- 将int7ch入口地址指向0:200
安装程序:
assume cs:code
stack segment
db 128 dup (0)
stack ends
code segment
start: mov ax,stack
mov ss,ax
mov sp,128
push cs
pop ds
mov ax,0
mov es,ax
mov si,offset setscreen ;设置ds:si指向源地址
mov di,204h ;设置es:di指向目标地址
mov cx,offset setscreenend-offset setscreen ;设置cx为传输长度
cld ;设置传输方向为正
rep movsb
push es:[7ch*4]
pop es:[200h]
push es:[7ch*4+2]
pop es:[202h] ;暂存BIOS原有的int7c中断例程地址
cli
mov word ptr es:[7ch*4],204h
mov word ptr es:[7ch*4+2],0 ;设置新的int7c中断例程的地址0:204h
sti
mov ax,4c00h
int 21h
org 204h ;写给编译器执行的, 本行以后的程序会以204h为基础进行偏移计算
setscreen: jmp short set
;------------
;table 表中存储的的标号地址是在编译时计算的, 如果不做任何处理, 是以主程序的标号地址来计算的(sub1 即表示 sub1-start+0).
; 所以安装到中断例程中后, 会导地址指向错误. 因为在执行中断例程执行时(cs)=0,(ip)=204h. sub1真正应该表示的标号地址为sub1-setscreen+204h
;两种解决方案
;1. 手动计算偏移地址`table dw sub1-setscreen+204h,sub2,sub3,sub4`
;2. 在安装中断例程之前设置`org 204h`,通知编译器重新计算偏移
table dw sub1,sub2,sub3,sub4
set: push bx
cmp ah,3
ja sret ;大于3就退出
mov bl,ah
mov bh,0
add bx,bx ;根据ah中的功能号计算对应子程序在table表中的偏移
call word ptr table[bx] ;调用对应的子程序
sret: pop bx
iret
;----------清屏-----------
sub1: push bx
push cx
push es
mov bx,0b800h
mov es,bx
mov bx,0
mov cx,2000
sub1s: mov byte ptr es:[bx], ' '
add bx,2
loop sub1s
pop es
pop cx
pop bx
ret
;----------设置前景色-----------
sub2: push bx
push cx
push es
mov bx,0b800h
mov es,bx
mov bx,1
mov cx,2000
sub2s: and byte ptr es:[bx], 11111000b
or es:[bx],al
add bx,2
loop sub2s
pop es
pop cx
pop bx
ret
;----------设置背景色-----------
sub3: push bx
push cx
push es
mov cl,4
shl al,cl
mov bx,0b800h
mov es,bx
mov bx,1
mov cx,2000
sub3s: and byte ptr es:[bx],10001111b
or es:[bx],al
add bx,2
loop sub3s
pop es
pop cx
pop bx
ret
;----------向上滚动一行-----------
sub4: push cx
push si
push di
push es
push ds
mov si,0b800h
mov es,si
mov ds,si
mov si,160 ;ds:si指向第1行
mov di,0 ;es:di指向第0行
cld ;设置传输方向为正
mov cx,24 ;共复制24行
sub4s: push cx
mov cx,160 ;每行有160个字节
rep movsb ;复制
pop cx
loop sub4s
mov cx,80
mov si,0
sub4s1:mov byte ptr [160*24+si], ' ' ;最后一行清空
add si,2
loop sub4s1
pop ds
pop es
pop di
pop si
pop cx
ret
setscreenend: nop
code ends
end start
测试程序1(清屏):
assume cs:code
code segment
mov ah,0
int 7ch
mov ax,4c00h
int 21h
code ends
end
测试程序2(设置前景色):
assume cs:code
code segment
mov ah,1
mov al,1
int 7ch
mov ax,4c00h
int 21h
code ends
end
测试程序3(设置背景色):
assume cs:code
code segment
mov ah,2
mov al,2
int 7ch
mov ax,4c00h
int 21h
code ends
end
测试程序4(向上滚动一行):
assume cs:code
code segment
mov ah,3
int 7ch
mov ax,4c00h
int 21h
code ends
end