1. 预备知识
-
当数据存放在内存中时,我们可以用多种方式来定位该数据,这个过程称为寻址,即寻找数据所在内存单元的段地址和偏移地址。
-
在 8086CPU 中,只有 bx、si、di、bp 这四个寄存器可以用在中括号内进行内存单元的寻址。这四个寄存器可以单独出现,或只能以四种组合出现:[bx+si]、[bx+di]、[bp+si]、[bp+di]。
-
8086CPU 指令处理的数据长度包括字节和字两种,在处理数据时指定处理长度的方式:通过寄存器,如使用 AX 表示处理字数据、使用 AL 表示处理字节数据;在没有寄存器参与的内存单元的访问中,通过关键字 word 或 byte 显式指定,如
mov byte ptr [di],2h。 -
[bx+si+idata] 的寻址方式为结构化数据的处理提供了方便:bx 定位整个结构体,idata 定位结构体的某一项,si 定位项中的元素。
-
div 是汇编语言中的除法指令。对于除数而言,有 8 位和 16 位两种。对于被除数,如果除数为 8 位则被除数为 16 位,默认存放在 AX 中;如果除数为 16 位则被除数为 32 位,DX 存放高 16 位、AX 存放低 16位。对于结果,如果除数为 8 位,则 AL 存放商、AH 存放余数;如果除数为 16 位,则 AX 存放商、DX 存放余数。
2. 实验任务 1:修改单词中的字母
将 data 段中每个单词中的前四个字母改为大写:
assume cs:code,ds:data
data segment
db '1. display '
db '2. brows '
db '3. replace '
db '4. modify '
data ends
code segment
start:
;待完成部分
code ends
end start
2.1 实验分析
在数据段中,字符串后面部分使用空格填充至 16 字节,其在内存中以如下方式组织:
基于 [bx+si+idata] 的寻址方式,使用 bx 定位每个字符串、si 定位每个字母、在处理字符串时 idata 定位从第 4 个字符开始处理。包含两个变量,使用双重循环,外层循环用于遍历四个单词,内层循环用于遍历四个字母。
由于还没有介绍比较指令的使用,这里使用位运算将小写字母转换为大写字母。整体代码为:
assume cs:code,ds:data
data segment
db '1. display '
db '2. brows '
db '3. replace '
db '4. modify '
data ends
code segment
start:
mov ax,data
mov ds,ax ;使用段寄存器DS指向数据段data
mov bx,0
mov cx,4 ;外层循环次数
s1:
push cx ;保护外层循环CX的值
mov si,0
mov cx,4 ;内层循环次数
s2:
mov al,[bx+si+3]
and al,11011111b ;按位与运算将小写字母转换为大写字母
mov [bx+si+3],al
inc si ;内层循环每次偏移1个字节处理字母
loop s2
add bx,16 ;外层循环每次偏移16个字节处理单词
pop cx ;恢复外层循环CX的值
loop s1
mov ax,4c00h
int 21h
code ends
end start
2.2 实验结果
程序运行前,数据段在内存中的形式:
3. 实验任务 2:结构化数据的访问
下面是某公司从 1795 年到 1995 年的基本情况:
下面程序已完成数据定义:
assume cs:codeseg
data segment
db '1975','1976','1977','1978','1979','1980','1981','1982','1983'
db '1984','1985','1986','1987','1988','1989','1990','1991','1992'
db '1993','1994','1995'
;存放年份,每一项用4个字节表示
dd 16,22,382,1356,2390,8000,16000,24486,50065,97479,140417,197514
dd 345980,590827,803530,1183000,1843000,2759000,3753000,4649000,5937000
;存放收入,每一项用4个字节表示
dw 3,7,9,13,28,38,130,220,476,778,1001,1442,2258,2793,4037,5635,8226
dw 11542,14430,15257,17800
;存放雇员,每一项用2个字节表示
data ends
table segment
db 21 dup ('year summ ne ?? ')
;表格段,年份(4)用year初始化、收入(4)用summ初始化、雇员(2)用ne初始化、人均收入(2)用??初始化
table ends
编程,将 data 段中的数据按如下格式写入 table 段中,并计算每年的人均收入并保存到 table 段。
3.1 实验分析
采取每一项单独分析,最后合并的思路。首先往 table 段写入表示年份的字符串,每一项用四个字节表示。考虑每次写入一个字,使用两个连续的 mov 指令即可。该部分代码为:
mov ax,ds:[si]
mov es:[bp],ax ;写入前两个字节
mov ax,ds:[si+2]
mov es:[bp+2],ax ;写入后两个字节
收入字段使用四个字节表示,采用和年份字段相似的处理。在 data 段中,收入部分的数据相对于年份的偏移为 42×4=168 字节;在 table 段,收入部分的数据相对于年份的偏移为 5 字节。该部分代码为:
mov ax,ds:[84+si]
mov es:[bp+5],ax ;写入前两个字节
mov ax,ds:[84+si+2]
mov es:[bp+5+2],ax ;写入后两个字节
雇员字段使用两个字节表示,使用一个 mov 语句即可。雇员部分的数据相对于年份的偏移为 21×4=84 字节;在 table 段,雇员部分的数据相对于年份的偏移为 10 字节。该部分代码为:
mov ax,ds:[168+si]
mov es:[bp+10],ax
最后,对于人均收入字段,由收入字段和雇员字段做除法得到。由于除数雇员字段为 16 位,所以被除数为 32 位,且 DX 存放高 16 位、AX 存放低 16 位。人均收入的数据相对于年份的偏移为 13。该部分代码为:
mov ax,ds:[84+si] ;AX存放低16位
mov dx,ds:[84+si+2] ;DX存放高16位
div word ptr ds:[168+di] ;word ptr指定除法运算的为16位,且结果存放在AX中(整除)
mov es:[bp+13],ax ;写入
上述代码中,si 用于辅助从 data 段取四字节的数据,每次偏移量为 4 字节;di 用于辅助从 data 段取两字节的数据,每次偏移量为 2 字节;bp 用于索引表格中的每一行数据,每次偏移量为 16 字节。最后,在外层加上循环量为 21 的循环,整体代码为:
assume cs:codeseg
data segment
db '1975','1976','1977','1978','1979','1980','1981','1982','1983'
db '1984','1985','1986','1987','1988','1989','1990','1991','1992'
db '1993','1994','1995'
;存放年份,每一项用4个字节表示
dd 16,22,382,1356,2390,8000,16000,24486,50065,97479,140417,197514
dd 345980,590827,803530,1183000,1843000,2759000,3753000,4649000,5937000
;存放收入,每一项用4个字节表示
dw 3,7,9,13,28,38,130,220,476,778,1001,1442,2258,2793,4037,5635,8226
dw 11542,14430,15257,17800
;存放雇员,每一项用2个字节表示
data ends
table segment
db 21 dup ('year summ ne ?? ')
;表格段,年份(4)用year初始化、收入(4)用summ初始化、雇员(2)用ne初始化、人均收入(2)用??初始化
table ends
codeseg segment
start:
mov ax,data
mov ds,ax ;段寄存器DS指向段data
mov ax,table
mov es,ax ;段寄存器ES指向段table
mov si,0
mov di,0
mov bp,0
mov cx,21
s:
mov ax,ds:[si]
mov es:[bp],ax ;先移动前两个字节
mov ax,ds:[si+2]
mov es:[bp+2],ax ;再移动后两个字节
mov ax,ds:[84+si] ;84是数据段中存放收入内存相对于存放年份的偏移
mov es:[bp+5],ax ;5是表格段中存放收入内存相对于存放年份的偏移,先移动前两个字节
mov ax,ds:[84+si+2]
mov es:[bp+5+2],ax ;再移动后两个字节
mov ax,ds:[168+di] ;168是数据段中存放收入内存相对于存放年份的偏移
mov es:[bp+10],ax ;10是表格段中存放收入内存相对于存放年份的偏移,每次处理两个字节
mov ax,ds:[84+si] ;AX存放低16位
mov dx,ds:[84+si+2] ;DX存放高16位
div word ptr ds:[168+di] ;word ptr指定除法运算为16位,且结果存放在AX中(整除)
mov es:[bp+13],ax ;写入
add si,4 ;每次偏移4个字节从data段中取4字节数据
add di,2 ;每次偏移2个字节从data段中取2字节数据
add bp,16 ;每次偏移16个字节写入下一行数据
loop s
mov ax,4c00h
int 21h
codeseg ends
end start
3.2 实验结果
程序运行前,table 段的内容为:
程序运行后,table 段的内容为:
4. 总结
-
寻址即定位数据在内存中的位置,本文主要介绍了寻址在结构化数据中的应用
-
div 是汇编语言的除法指令,有 8 位除法和 16 位除法两种。后续还将介绍除法溢出等相关内容
-
参考:汇编语言/王爽著.——北京:清华大学出版社,2003