1. 预备知识
-
在 8086CPU 中,只有 bx、si、di、bp 可用在中括号内进行内存单元的寻址,如 mov ax,[ax] 错误;且这四个寄存器的两两组合只能是:bx 和 si、bx 和 di、bp 和 si、bp 和 di,其余组合方式均错误。
-
寻址方式包括:
- [idata] 直接寻址
- [bx] 寄存器间接寻址
- [bx+idata] 寄存器相对寻址
- [bx+si] 基址变址寻址
- [bx+si+idata] 相对基址变址寻址
-
在 8086CPU 中提供的 [bx+si+idata] 的寻址方式为结构化数据的处理提供了方便:用 bx 定位整个结构体,用 idata 定位结构体中的某一数据项,用 si 定位数据项中的每个元素,上述寻址格式也可写作 [bx].idata[si]。
-
div 除法指令可以操作 8 位和 16 位两种数据:
- 除数:有 8 位和 16 位两种,在一个寄存器或内存单元中
- 被除数:默认放在 AX 或 DX 中,如果除数为 8 位则被除数为 16 位,默认在 AX 中存放;如果除数为 16 位则被除数为 32 位,在 AX 和 DX 中存放,DX 存放高 16 位、AX 存放低 16 位
- 结果:如果除数为 8 位,则 AL 存储商、AH 存储余数;如果除数为 16 位,则 AX 存储商、DX 存储余数
-
在没有寄存器参与的运算中,使用 X ptr 指明参与运算的数据长度,这在除法运算中经常使用,其中 X 为 word(16 字节)或 byte(8 字节)。
-
伪指令 dd 用于定义双字类型
-
重复指令 dup 和 db、dw、dd 等数据定义伪指令配合使用,用于进行数据的重复:
dd 3 dup (0) ;定义了3个字节,它们的值都是0,相当于 db 0,0,0
dd 3 dup (0,1,2) ;相当于 db 0,1,2,0,1,2,0,1,2
2. 实验任务
下面是某公司从 1975 年成立一直到 1995 年的基本情况:
年份 | 收入 | 雇员 | 人均收入 |
---|---|---|---|
1975 | 16 | 3 | ? |
1976 | 22 | 7 | ? |
1977 | 382 | 8 | ? |
1978 | 1356 | 13 | ? |
1979 | 2390 | 28 | ? |
1980 | 8000 | 38 | ? |
… | … | … | … |
1995 | 5937000 | 17800 | ? |
下面程序已经完成数据的定义:
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 段中,并计算 21 年中的人均收入(取整)并保存到 table 段中:
- 在 table 段中使用 dup 指令重复定义了 21 个字符串 "year summ ne ?? ",则内存空间 table:0~table:14FH 用于存放该公司 1975年~1995 年的基本情况。
- 使用段寄存器 BX 和段寄存器 ES 分别指向数据段 data 和表格段 table。
mov ax,data
mov ds,ax ;段寄存器DS指向段data
mov ax,table
mov es,ax ;段寄存器ES指向段table
- 首先处理表示年份的字符串,每一项用 4 个字节表示,可以连续使用两条 mov 语句完成,每次移动 2 个字节,形式为 mov es:[bp],ds:[bx]、mov es:[bp+2],dp:[bx+2]。
- 处理完一项后,寄存器 BP 偏移 4 字节以指向数据段的下一项;寄存器 BX 偏移 16 字节以指向表格段的下一行。该部分代码为:
mov bx,0
mov bp,0
mov cx,21
s: mov ax,ds:[bx]
mov es:[bp],ax ;先移动前两个字节
mov ax,ds:[bx+2]
mov es:[bp+2],ax ;再移动后两个字节
add bx,4 ;每次偏移4个字节从data段中取数据
add bp,16 ;每次偏移16个字节写入下一行数据
loop s
- 其次处理表示收入的整数,使用 4 个字节表示。和处理年份时类似,使用两次移动完成,只是在从 data 中取数据时要加上存储年份区域的偏移,在存数据时也要加上相应的偏移。
- 同样地,处理完一项后,寄存器 BP 偏移 4 字节以指向数据段的下一项;寄存器 BX 偏移 16 字节以指向表格段的下一行。该部分代码为:
mov bx,0
mov bp,0
mov cx,21
s: mov ax,ds:[84+bx] ;84是数据段中存放收入内存相对于存放年份的偏移
mov es:[bp+5],ax ;先移动前两个字节
mov ax,ds:[84+bx+2]
mov es:[bp+5+2],ax ;再移动后两个字节
add bx,4 ;每次偏移4个字节从data段中取数据
add bp,16 ;每次偏移16个字节写入下一行数据
loop s
- 然后处理雇员这一项,经过前面两项的处理后,这一项的处理类似。该部分代码为:
mov bx,0
mov bp,0
mov cx,21
s: mov ax,ds:[168+bx] ;168是数据段中存放收入内存相对于存放年份的偏移
mov es:[bp+10],ax ;10是表格段中存放收入内存相对于存放年份的偏移,每次处理两个字节
add bx,2 ;每次偏移2个字节从data段中取数据
add bp,16 ;每次偏移16个字节写入下一行数据
loop s
- 由于人均收入等于收入除以雇员数,根据预备知识部分,除数(雇员数)为 16 为则被除数(收入)为 32 位,且 DX 存放高 16 位,AX 存放低 16 位。此时结果的存放情况为:AX 存储商,DX 存储余数,由于题目只使用 16 位存储结果,所以忽略余数的处理,所以除法结果存放在寄存器 AX 中。
- 根据前面的程序,被除数(收入)的低位存放在 [84+bx] 的内存单元中,高位存放在 [84+bx+2] 的内存单元中(由下图可知,存放形式为小端序);除数(雇员)存放在 [bx+168] 中。
- 同时,使用 word ptr 指明每次除法运算时参与计算的数值长度为 16 位。该部分代码为:
mov bx,0
mov bp,0
mov si,0
mov cx,21
s: mov ax,ds:[84+bx] ;AX存放低16位
mov dx,ds:[84+bx+2] ;DX存放高16位
div word ptr ds:[168+si];word ptr指定除法运算的数值为16位,且结果存放在AX中(不考虑余数)
mov es:[bp+13],ax ;存入表格中
add bx,4 ;每次偏移4个字节从data段中取被除数数据
add si,2 ;每次偏移2个字节从data段中取除数数据
add bp,16 ;每次偏移16个字节写入下一行数据
loop s
至此已经完成全部功能,将代码整合:
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 bx,0
mov bp,0
mov cx,21
s1: mov ax,ds:[bx]
mov es:[bp],ax ;先移动前两个字节
mov ax,ds:[bx+2]
mov es:[bp+2],ax ;再移动后两个字节
add bx,4 ;每次偏移4个字节从data段中取数据
add bp,16 ;每次偏移16个字节写入下一行数据
loop s1
mov bx,0
mov bp,0
mov cx,21
s2: mov ax,ds:[84+bx] ;96是数据段中存放收入内存相对于存放年份的偏移
mov es:[bp+5],ax ;5是表格段中存放收入内存相对于存放年份的偏移,先移动前两个字节
mov ax,ds:[84+bx+2]
mov es:[bp+5+2],ax ;再移动后两个字节
add bx,4 ;每次偏移4个字节从data段中取数据
add bp,16 ;每次偏移16个字节写入下一行数据
loop s2
mov bx,0
mov bp,0
mov cx,21
s3: mov ax,ds:[168+bx] ;168是数据段中存放收入内存相对于存放年份的偏移
mov es:[bp+10],ax ;10是表格段中存放收入内存相对于存放年份的偏移,每次处理两个字节
add bx,2 ;每次偏移2个字节从data段中取数据
add bp,16 ;每次偏移16个字节写入下一行数据
loop s3
mov bx,0
mov bp,0
mov si,0
mov cx,21
s4: mov ax,ds:[84+bx] ;AX存放低16位
mov dx,ds:[84+bx+2] ;DX存放高16位
div word ptr ds:[168+si];word ptr指定除法运算的数值为16位,且结果存放在AX中(不考虑余数)
mov es:[bp+13],ax ;存入表格中
add bx,4 ;每次偏移4个字节从data段中取被除数数据
add si,2 ;每次偏移2个字节从data段中取除数数据
add bp,16 ;每次偏移16个字节写入下一行数据
loop s4
mov ax,4c00h
int 21h
codeseg ends
end start
使用 DEBUG 查看运行前数据段的内容。红色为存储年份的区域,蓝色为存储收入的区域,绿色为存储雇员的区域,白色为填充区域。
使用 DEBUG 查看运行前后表格段的内容:
如图,红色为存储年份的区域,蓝色为存储收入的区域,绿色为存储雇员的区域,白色存储人均收入的区域:
上述代码使用 4 个循环完成,可以对部分循环进行嵌套以简化代码:
- 最外层循环 21 次,这个基本部分不变。
- 在上述循环中,寄存器 BX 的值每次偏移 2 或 4 个字节,所以需使用两个寄存器分别表示,这里分别使用 SI 和 DI。
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] ;96是数据段中存放收入内存相对于存放年份的偏移
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段中取数据
add di,2 ;每次偏移2个字节从data段中取数据
add bp,16 ;每次偏移16个字节写入下一行数据
loop s
mov ax,4c00h
int 21h
codeseg ends
end start
结果和第一部分相同。