汇编语言(王爽第三版)实验7 寻址方式在结构化数据访问中的应用
实验7 寻址方式在结构化数据访问中的应用
问题提出:Power idea公司的基本情况如下:
见书中数据列表:
下面格式已经定义好了这些数据:
assume cs:code
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'
;以上是表示21年的21个字符串
dd 16,22,382,1356,2390,8000,16000,24486,50065,97479,140417,197514
dd 345980,590827,803530,1183000,1843000,2759000,3753000,4649000,5937000
;以上是表示21年 公司总收入的21个dword型数据
dw 3,7,9,13,28,38,130,220,476,778,1001,1442,2258,2793,4037,5635,8226
dw 11542,14430,15257,17800
;以上是表示21公司雇员人数的21个Word型数据。
data ends
table segment
db 21 dup ('year summ ne ?? ')
table ends
code segment
start:
???????
mov ax,4c00H
int 21H
code ends
end start
实验要求:将data段中的数据,按照书中的格式写入到table段中,并计算21年中的人均收入(取整),结果也按照下面的格式保存在table段中。
实验分析:
此实验定义并初始化了2个数据段(注意段的表示方法),从源内存段data中将数据读取出来后,按照一定的格式要求(有的数据需要处理)写入到目的内存段table中。
实验目的:
1. 在一个数据段中定义不同的数据类型(字符、字符串和数值)的方式,并考察他们在内存中的存储情况。
2. 掌握dup的用法。
3. 考察我们对同一个数据段的内存单元,不同的寻址方式。
4. 掌握DIV除法的基本应用。
5. 怎样结构化的在内存中存储数据。
6. 熟练掌握我们以前所学习的寄存器的用法,并归纳总结。
程序分析:
考虑到此程序使用了2个数据段,并且我们同时要使用这2个数据段,我们除了使用通用的数据段寄存器DS外,另外使用ES寄存器来进行另外一个数据段的寻址。这样我们指定data段的段地址存储在DS中,table段的段地址存储在ES中。
在data段中,我们观察并考虑怎样使用灵活多变的寻址方式来访问内存(这时候我们在脑海中应该有对于data段数据的内存存储有映像了)。通俗讲就是怎样在内存中寻址。
1. 程序定义并初始化了data段,考虑程序中一共使用了3中数据类型,单字节类型(一个字节)、Word字类型(2个字节)、DW双字类型(4个字节)。它们在内存单元中所占用的连续空间是不同的。(问题提出:为什么要使用不同的数据类型?这个在C语言及其他高级语言中同样适用。不同的数据类型匹配不同的数据,做到合理安排数据在内存中存储,不浪费空间,提高运行速度。)
因为在data段中,这些初始化的数据在内存中是连续存储的。(这时如果我们对于他们在内存中的存储有疑问,可以使用debug来查看下它们具体的存储情况,这样比较直观的体验下)。例如:部分存储情况(怎么来的?不写其他代码,编译连接debug后得出的):
-d ds:00
0B65:0000 31 39 37 35 31 39 37 36-31 39 37 37 31 39 37 38 1975197619771978
0B65:0010 31 39 37 39 31 39 38 30-31 39 38 31 31 39 38 32 1979198019811982
0B65:0020 31 39 38 33 31 39 38 34-31 39 38 35 31 39 38 36 1983198419851986
0B65:0030 31 39 38 37 31 39 38 38-31 39 38 39 31 39 39 30 1987198819891990
0B65:0040 31 39 39 31 31 39 39 32-31 39 39 33 31 39 39 34 1991199219931994
0B65:0050 31 39 39 35 10 00 00 00-16 00 00 00 7E 01 00 00 1995........~...
0B65:0060 4C 05 00 00 56 09 00 00-40 1F 00 00 80 3E 00 00 L...V...@....>..
0B65:0070 A6 5F 00 00 91 C3 00 00-C7 7C 01 00 81 24 02 00 ._.......|...$..
-d
0B65:0080 8A 03 03 00 7C 47 05 00-EB 03 09 00 CA 42 0C 00 ....|G.......B..
0B65:0090 18 0D 12 00 38 1F 1C 00-58 19 2A 00 28 44 39 00 ....8...X.*.(D9.
0B65:00A0 28 F0 46 00 68 97 5A 00-03 00 07 00 09 00 0D 00 (.F.h.Z.........
0B65:00B0 1C 00 26 00 82 00 DC 00-DC 01 0A 03 E9 03 A2 05 ..&.............
0B65:00C0 D2 08 E9 0A C5 0F 03 16-22 20 16 2D 5E 38 99 3B ........" .-^8.;
0B65:00D0 88 45 00 00 00 00 00 00-00 00 00 00 00 00 00 00 .E..............
0B65:00E0 79 65 61 72 20 73 75 6D-6D 20 6E 65 20 3F 3F 20 year summ ne ??
0B65:00F0 79 65 61 72 20 73 75 6D-6D 20 6E 65 20 3F 3F 20 year summ ne ??
data数据段:
我们发现:.
年份:在0b65:0000~0053H中存储的是21个字符串(没0结尾的)。(21个字符串它所占内存空间为21*4=84Bytes);黑色表示
收入:在0b65:054~00A7H中存储的是21个dword数据(21个dword型数据,它所占内存空间为:21*4=84Bytes);(??看下开始,它们都是4字节的。第一个数存储的是10H,就是16,剩下3个高位单元都是00H,对了,它就代表是16;最后,68 97 5A 00 实际它是005A9768H=593700)红色表示
雇员人数:00A8~00D1中存储的是21个word型数据,它所占内存空间为:21*2 = 42Bytes;绿色表示。
为什么后面的14个字节都是00H?是否浪费了?因为程序又重新定义并初始化了一个数据段用来存放year summ ne ??了。那么14个字节基本就是浪费了。
这时候我们考虑怎样来表示data段中的内存(能够表示了,那么就代表能够寻址了)
table数据段:
0B65:00E0开始是table数据段的内存地址了,如果偏移地址是0000H,那么它的段地址就是:0B73。 0B65:00E0H==0B73:0000H
定义时使用了db语句,定义的字符串正好是16个字节,在debug中就是一行。重复21行,也就是在table数据段有21行的'year summ ne ?? '(体会这样的好处,省的在table数据段重复定义21次了,一个dup语句就搞定了。)
强烈提示:这个是在debug中,为了显示方便,每行内存都是16个字节,但真正的内存是单列存储的,是没有行这个概念的。
对源数据的读取:
对于21个字符串的寻址:
考虑到这个程序就是整个复制字符串,而不是修改它的内容,所以我们的寻址方式应该简化。考虑该字符串都是占用4个字节,那么我们使用ax寄存器操作2次就可以把一个字符串复制了;我们采用[bx+idata]方式寻址就可以了。其中idata(0,2)。一个字符串:例如1975占用4个字节的内存,其中[bx+0]=3139H,[bx+2]=3735H.
bx的范围:0~83
对于21个dword型数据的寻址:
同样道理,通过寄存器传送2个字就可以了。
注意此时idata有变化了,idata初始值变成了84了(84,86)
对于21个word型数据的寻址:
同样道理,一个寄存器就搞定了。每次就一个字(8086CPU普通寄存器就是一个字),
注意此时idata又有变化了,idata的初始值变成了168(0+84+84=168)。
对目标内存的写入:
由于DS寄存器被使用了。我们不得不使用ES寄存器来存储table数据段的基地址(段地址)。(ES)= (table)
由于bx变址寄存器被使用了(ax不能用、cx作为计数器、dx呢做除法去了。呵呵),我们再使用其他的个变址寄存器之一就行了。
我们可以使用si寄存器。那么通过es:si就可以对目标内存进行寻址了。
还有一个问题就是对于雇员数的地址,它的规律是每次+2的偏移,需要再搞个寄存器来进行变址,我这用了di。(因为在前面2个数据都是4个字节的,偏移量+4可以,但雇员数应该是+2)
对于空格的处理:由于空格的ASCII码是:32(20H)。占用一个字节。这时候我们要注意传送的是一个字节,不是一个字,故应该使用的是8位寄存器,例如:al。
关于结构化内存的写入:我们发现实验中要求我们在一行(16个字节中)正好写入这些数据(包括空格),那么我们注意空格的写入会导致在变址寄存器变量的有规律变化,在debug中就是按照一行的内存显示,其实他们的规律变化都是基于16个字节。
表格一行的组成如下(含偏移地址si=0000H,段地址在ES中,这个可以画图说明):
年份(4个字节):(si)= 0000H~0003H (si +0 is +2)
空格: (si)= 0004H (is+4)
收入(4个字节):(si)= 0005H~0008H (si +5 is + 7)
空格 (si)= 0009H (si +9)
雇员数(2个字节)(si)= 000aH~000bH (si +10)
空格: (si)= 000cH (si +12)
人均收入(2个字节)(si)= 000dH~000eH (si + 13)
空格 (si)= 000fH (si + 15)
关于除法在此题目中的应用:
我们发现:被除数(总收入)是32位的,除数(雇员数)是16位的,那么在做除法时它们应该怎样存储?
被除数:低16位的存储在ax中,高16位的存储在dx中;那么(ax)= (bx+84);
(dx) = (bx+86)。相应代码为:
mov ax,[bx+84]
mov dx,[bx+86]
除数:我们将除数存储在bp寄存器中吧,因为考虑以后有寄存器的冲突。
mov bp,[di+168]
div bp
mov es:[si+13],ax
除法的应用,我们有个小专题专门讲解下。
最终的代码如下:
assume ds:data, es:table, cs:code
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'
;以上是表示21年的21个字符串
dd 16,22,382,1356,2390,8000,16000,24486,50065,97479,140417,197514
dd 345980,590827,803530,1183000,1843000,2759000,3753000,4649000,5937000
;以上是表示21年 公司总收入的21个dword型数据
dw 3,7,9,13,28,38,130,220,476,778,1001,1442,2258,2793,4037,5635,8226
dw 11542,14430,15257,17800
;以上是表示21公司雇员人数的21个Word型数据。
data ends
table segment
db 21 dup ('year summ ne ?? ')
table ends
code segment
start:
;初始化2个数据段,将ds指向data,es指向table
mov ax,data
mov ds,ax
mov ax,table
mov es,ax
;初始化偏址寄存器变量
mov bx,0
mov si,0
mov di,0
;共21行,循环21次,初始化计数器
mov cx,21
s:
;写入年份
mov ax,0[bx] ;如看着别扭,改成mov ax, [bx+0]
mov es:0[si],ax
mov ax,2[bx]
mov es:2[si],ax
;写入空格
mov al,20H
mov es:4[si],al
;写入收入
mov ax,84[bx]
mov es:5[si],ax
mov ax,86[bx]
mov es:7[si],ax
;写入空格
mov al,20H
mov es:9[si],al
;雇员数
mov ax,168[di]
mov es:10[si],ax
;写入空格
mov al,20H
mov es:12[si],al
;人均收入,高16位送入dx,低16位送入ax
mov ax,[bx+84]
mov dx,[bx+86]
;用个bp变量存储除数,为以后实验考虑
mov bp,[di+168]
div bp ;16位除法指令
mov es:13[si],ax ;将商的结果(ax)写入table段中
;写入空格
mov al,20H
mov es:15[si],al
;bx、si、di变量的递增
add bx,4 ;年份和总收入都是双字单元,故bx的递增量是4
add si,16 ;table中每行是16个字节,偏移量为16
add di,2 ;人数是字单元,故di的递增量是2
loop s
mov ax,4c00H
int 21H
code ends
end start
实验结果:编译、连接后,在debug中,查看table段内存:
-d es:0
0B73:0000 31 39 37 35 20 10 00 00-00 20 03 00 20 05 00 20 1975 .... .. ..
0B73:0010 31 39 37 36 20 16 00 00-00 20 07 00 20 03 00 20 1976 .... .. ..
0B73:0020 31 39 37 37 20 7E 01 00-00 20 09 00 20 2A 00 20 1977 ~... .. *.
0B73:0030 31 39 37 38 20 4C 05 00-00 20 0D 00 20 68 00 20 1978 L... .. h.
0B73:0040 31 39 37 39 20 56 09 00-00 20 1C 00 20 55 00 20 1979 V... .. U.
0B73:0050 31 39 38 30 20 40 1F 00-00 20 26 00 20 D2 00 20 1980 @... &. ..
0B73:0060 31 39 38 31 20 80 3E 00-00 20 82 00 20 7B 00 20 1981 .>.. .. {.
0B73:0070 31 39 38 32 20 A6 5F 00-00 20 DC 00 20 6F 00 20 1982 ._.. .. o.
……
……
程序总结:
1)这个实验是将空格作为每一个数据的结尾,对于字符串来说,结尾应该是0。便于将来判断是否为字符串。
2)可以把table段中的一行看做是结构体。
3)加深数据在内存中存储的形式,并在脑海中抽象出来。