16.1 描述了单元长度的标号
assume cs:code
code segment
adb 1,2,3,4,5,6,7,8
bdw 0
start: mov si,0
mov cx,0
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”:
- 指令:mov ax,b
- 相当于:mov ax,cs:[8] 下面的指令会引起编译错误:
- mov al,b
- 因为b代表的内存单元是字单元,而al是8位寄存器
使用这种包含单元长度的标号,可以使我们以简洁的形式访问内存中的数据。这种标号称为 数据标号,它标记了存储数据的单元地址和长度。它不同于仅仅表示地址的 地址标号
-
我自己对数据标号、地址标号、offset、seg的总结
- (1)之前见到的标号都是地址标号,代表内存单元的地址;这章学到的数据标号,不仅代表内存单元地址, 还代表了此地址处数据的单元长度。
-
(2)本书中谈到内存单元地址可以拆分成
段地址:偏移地址,第9章学到的伪指令“offset 标号”,就是获得标号处的内存单元的偏移地址;下一小节学到的“seg 标号”操作符,就是获得标号处的段地址。 -
(3)在分析“seg”标号时,我总是纠结seg操作符怎么从标号中得到段地址的。后来我想明白了,我从了解到offset伪指令开始,到现在所写的检测点、每章的实验的代码,都把标号当作偏移地址用。
其实标号代表的是内存单元的地址,包括了段地址和偏移地址。只有“offset 标号”,得到的才是偏移地址。 学了这章之后还要注意:地址标号和数据标号的区别。
16.2 在其它段中使用数据标号
一般来说,我们不在代码段中定义数据,而是将数据定义到其它段中。在其它段中,我们也可以使用数据标号来描述存储数据的单元的地址和长度。注意,在后面加有“:”的地址标号,只能在代码段中使用,不能在其它段中使用。
如果想在代码段中直接使用数据标号访问数据,则需要用伪指令assume将标号所在的段和一个段寄存器联系起来。否则编译器在编译的时候,无法确定标号的段地址在哪个寄存器中。当然,这种联系是编译器需要的,但决定不是说,我们用assume指令将段寄存器和某个段相联系,段寄存器中就会真的存放该段的地址。 我们在程序中还要使用指令对寄存器进行设置。
可以将标号当作数据来定义,此时,编译器将标号所表示的地址当作数据值。
data segment
a db 1,2,3,4,5,6,7.8
b dw 0
c dw a,b
d dd a,b
data ends
数据标号c处存储的两个字型数据为标号a、b的偏移地址
数据标号d处存储的两个双字型数据为标号a的偏移地址和段地址、标号b的偏移地址和段地址
上面因为dw和dd的不同得到不同的地址要细细体会
seg操作符,功能为取得某一标号的段地址。 参考offset
16.3 直接定址表
可以通过依据数据,直接计算出所要找的元素的位置的表,我们称其为**直接定址表**。
结合我以前学习C语言指针的知识,我对这小节的理解就是:合理的利用资源建立映射关系,方便我们查找数据。
要注意利用数据标号访问数据时,虽然数据标号包含数据的单元长度,但偏移的计算还是以字节为单位。
水平有限,个人代码仅供参考:(比书中代码,附加了判断角度值是否在范围内)
assume cs:code,ds:data
data segment
tableAg db 0,30,60,90,120,150,180
table dw ag0,ag30,ag60,ag90,ag120,ag150,ag180
string db 'The angle is not in the range !',0
ag0 db '0',0
ag30 db '0.5',0
ag60 db '0.866',0
ag90 db '1',0
ag120 db '0.866',0
ag150 db '0.5',0
ag180 db '0',0
data ends
code segment
start:
mov ax,data
mov ds,ax
mov al,91 ;输入角度
call jdgAgl ;判断输入角度是否在能显示的范围内
call getVal ;显示角度对应的sin值
mov ax,4c00h
int 21h
;功能:判断输入的角度数是否在tableAg的范围内,如果不在屏幕就显示“The angle is not in the range!”
;参数:al=输入的角度
;返回:无
jdgAgl:
push si
push cx
push ax
push ds
push si
mov si,0
mov cx,7
cycJdg: cmp al,tableAg[si] ;循环判断输入的角度,是否在范围内
je endJdg
inc si
loop cycJdg
mov ax,data
mov ds,ax
mov si,offset string
call showStr
jmp reDos
endJdg: pop si ;角度判断通过,返回程序,继续运行
pop ds
pop ax
pop cx
pop si
ret
reDos: mov ax,4c00h ;角度判断不通过,结束程序返回dos系统
int 21h
;功能:根据输入角度得到对应的sin值
;参数:al=输入角度
;返回:无
getVal:
push ax
push bx
push si
mov ah,0
mov bl,30
div bl ;根据输入角度计算表中的索引
mov bl,al
mov bh,0
add bx,bx ;根据表中数据单元的长度(byte/word/dword)来调整访问表的索引
mov si,table[bx]
mov ax,data
mov ds,ax
call showStr
pop si
pop bx
pop ax
ret
;功能:显示指定地址处以0结尾的字符串
;参数:ds=字符串的段地址 si=字符串偏移地址
;参数:无
showStr:
push ax
push es
push di
push si
mov ax,0b800h
mov es,ax
mov di,12*160+30*2 ;es:di指向显存中显示字符串的首地址
cycSh: mov al,[si] ;显示字符串
cmp al,0
je endCyc ;遇到字符串结尾的0,结束循环
mov es:[di],al
inc si
add di,2
jmp short cycSh
endCyc: pop si
pop di
pop es
pop ax
ret
code ends
end start
16.4 程序入口地址的直接定址表
如果你理解了直接定址表,那这小节没什么新内容,核心还是不变的,你只要设计好数值和表之间的映射关系。
水平有限,个人代码仅供参考:(比书中代码,增加了一个对颜色值范围的判断)
assume cs:code,ds:data
data segment
tabCol db 0,1,2,3,4,5,6,7
string db 'the color is not in the range!',0
data ends
code segment
table dw clScreen,setFroCol,setBacCol,upRoll
start: mov ax,data
mov ds,ax
mov ax,0b800h
mov es,ax
mov ah,0 ;选择功能
mov bl,ah
mov bh,0
add bx,bx
mov al,4 ;传递颜色值
mov ah,0
call word ptr table[bx]
mov ax,4c00h
int 21h
;功能:清屏
;参数:es=显存段地址
;返回:无
clScreen:
push cx
push di
mov cx,2000
mov di,0
cycCl: mov byte ptr es:[di],' '
add di,2
loop cycCl
pop di
pop cx
ret
;功能:设置前景色
;参数:es=显存段地址 al=前景色 范围0~7
;返回:无
setFroCol:
push cx
push ax
push di
call jdgCol ;判断al的值在不在允许范围内
mov cx,2000 ;准备设置前景色
mov di,1
cycSfr: and byte ptr es:[di],11111000b ;循环设置前景色
or es:[di],al
add di,2
loop cycSfr
pop di
pop ax
pop cx
ret
;功能:设置背景色
;参数:es=显存段地址 al=前景色 范围0~7
;返回:无
setBacCol:
push cx
push ax
push di
call jdgCol ;判断al的值在不在允许范围内
mov cl,4
shl al,cl ;左移,将颜色值移动到背景色的位置
mov cx,2000 ;准备设置背景色
mov di,1
cycSba: and byte ptr es:[di],10001111b ;循环设置背景色
or es:[di],al
add di,2
loop cycSba
pop di
pop ax
pop cx
ret
;功能:向上滚动一行
;参数:无
;返回:无
upRoll:
push ax
push ds
push es
push si
push di
push cx
mov ax,0b800h
mov ds,ax
mov es,ax
mov si,160
mov di,0
cycRo: mov cx,80
cld
rep movsw
cmp si,4000
jne cycRo
mov cx,80
mov si,0
laRow: mov byte ptr es:[160*24+si],' ' ;最后一行设为空
add si,2
loop laRow
pop cx
pop di
pop si
pop es
pop ds
pop ax
ret
;功能:显示指定地址处以0结尾的字符串
;参数:ds=字符串的段地址 si=字符串偏移地址
;参数:无
showStr:
push ax
push es
push di
push si
mov ax,0b800h
mov es,ax
mov di,12*160+30*2 ;es:di指向显存中显示字符串的首地址
cycSh: mov al,[si] ;显示字符串
cmp al,0
je endCyc ;遇到字符串结尾的0,结束循环
mov es:[di],al
inc si
add di,2
jmp short cycSh
endCyc: pop si
pop di
pop es
pop ax
ret
;功能:判断输入的颜色值是否在给定的范围内
;参数:al=输入的颜色值
;返回:无
jdgCol:
push si
push cx
push ax
mov si,0
mov cx,8
cycJdg: cmp al,tabCol[si] ;判断al中前景色值在不在允许的范围内
je endJdg
inc si
loop cycJdg
mov si,offset string
call showStr
mov ax,4c00h ;如果输入的颜色值不在允许的范围内,直接结束程序
int 21h
endJdg: pop ax
pop cx
pop si
ret
code ends
end start
实验16 编写包含多个功能子程序的中断例程
这个实验的重点要关注,中断例程安装完成后,中断例程调用其它子程序时,地址的设置。在安装程序中,中断例程不会执行,安装程序中所有对内存访问的地址基础,是安装程序所在的地址空间,这个空间是DOS系统分配的;安装完成后,中断例程对内存访问的地址基础,是安装的位置。我的实验里中断例程安装在0000:0200处,所以中断例程中代码访问内存的地址基础就是0000:0200。这里的区别要注意,要仔细体会,如果你之前了解过高级语言关于“与位置无关代码”的介绍,那理解很容易。
水平有限,个人代码仅供参考:(代码内不包含,测试部分,有兴趣的安装完中断例程,自己写个单独的测试文件,我已测试通过)
assume cs:code
code segment
;安装程序,将中断例程安装到0000:0200处
start: mov ax,cs
mov ds,ax
mov si,offset int7c
mov ax,0
mov es,ax
mov di,0200h
mov cx,offset int7cE-offset int7c
cld
rep movsb
;修改中断向量表,使int 7c指向新中断例程
cli
mov ax,0
mov es,ax
mov word ptr es:[7ch*4],0200h
mov word ptr es:[7ch*4+2],0
sti
mov ax,4c00h
int 21h
;中断例程
int7c: push ax
push es
push bx
jmp short begi
table dw offset clScreen-offset int7c+0200h,offset setFroCol-offset int7c+0200h,offset setBacCol-offset int7c+0200h,offset upRoll-offset int7c+0200h
tabCol db 0,1,2,3,4,5,6,7
string db 'the color is not in the range!',0
begi: push ax
mov ax,0b800h
mov es,ax
pop ax
mov bl,ah ;功能号
mov bh,0
add bx,bx
call word ptr cs:[offset table-offset int7c+0200h+bx]
pop bx
pop es
pop ax
iret
clScreen: ;清屏
push cx
push di
mov cx,2000
mov di,0
cycCl: mov byte ptr es:[di],' '
add di,2
loop cycCl
pop di
pop cx
ret
setFroCol: ;设置前景色
push cx
push ax
push di
call jdgCol ;判断al的值在不在允许范围内
mov cx,2000 ;准备设置前景色
mov di,1
cycSfr: and byte ptr es:[di],11111000b ;循环设置前景色
or es:[di],al
add di,2
loop cycSfr
pop di
pop ax
pop cx
ret
setBacCol: ;设置背景色
push cx
push ax
push di
call jdgCol ;判断al的值在不在允许范围内
mov cl,4
shl al,cl ;左移,将颜色值移动到背景色的位置
mov cx,2000 ;准备设置背景色
mov di,1
cycSba: and byte ptr es:[di],10001111b ;循环设置背景色
or es:[di],al
add di,2
loop cycSba
pop di
pop ax
pop cx
ret
upRoll: ;向上滚动
push ax
push ds
push si
push di
push cx
mov ax,0b800h
mov ds,ax
mov si,160
mov di,0
cycRo: mov cx,80
cld
rep movsw
cmp si,4000
jne cycRo
mov cx,80
mov si,0
laRow: mov byte ptr es:[160*24+si],' ' ;最后一行设为空
add si,2
loop laRow
pop cx
pop di
pop si
pop ds
pop ax
ret
showStr: ;显示字符串
push ax
push di
push si
mov di,12*160+30*2 ;es:di指向显存中显示字符串的首地址
cycSh: mov al,[si] ;显示字符串
cmp al,0
je endCyc ;遇到字符串结尾的0,结束循环
mov es:[di],al
inc si
add di,2
jmp short cycSh
endCyc: pop si
pop di
pop ax
ret
jdgCol: ;判断颜色值是否在范围内
push si
push cx
push ax
mov si,0
mov cx,8
cycJdg: cmp al,cs:[offset tabcol-offset int7c+200h+si] ;判断al中前景色值在不在允许的范围内
je endJdg
inc si
loop cycJdg
mov si,offset string-offset int7c+200h
call showStr
mov ax,4c00h ;如果输入的颜色值不在允许的范围内,直接结束程序
int 21h
endJdg: pop ax
pop cx
pop si
ret
int7cE: nop
code ends
end start
2809




被折叠的 条评论
为什么被折叠?



