文章目录
前言
一、题目
二、分析
1.内存分配情况
2.数据结构分析
3.实现思路
(1)设置段寄存器
(2)复制“年份”数据
(3)复制“年总收入”数据
(4)复制“雇员人数”数据
(5)计算“人均收入”
三、代码
1.实现代码
2.优化代码
3.最终代码
总结
王爽老师《汇编语言》(第四版)第八章 实验7 寻址方式在结构化数据访问中的应用,题目分析以及代码。
题目:编程,将data段中的数据按照指定格式写到table段中。
题目代码如下。
assume cs:code,ds:data,ss:stack
data segment
db '1975','1976','1977','1978','1979','1980','1981','1982','1983','1984'
db '1985','1986','1987','1988','1989','1990','1991','1992','1993','1994','1995'
dd 16,22,382,1356,2390,8000,16000,24486,50065,97479,140417,197514
dd 345980,590827,803530,1183000,1843000,2759000,3753000,4649000,5937000
dw 3,7,9,13,28,38,130,220,476,778,1001,1442,2258,2793,4037,5635,8226
dw 11542,14430,15257,17800
data ends
table segment
db 21 dup ('year summ ne ?? ')
table ends
(1)首先,data中的数据可以看作三个数组,需要分别计算出这三个数组的起始元素的地址。
(2)最终数据要以table的方式呈现,可以看作是二维数组结构,于是考虑到要使用双层循环来遍历。那么外层循环的循环次数计数器的值,就要暂存到内存中(因为默认只有cx这一个寄存器是循环次数计数器),因此需要一段栈空间。
(3)接下来是table段,占21*16个字节。
段地址 | 偏移地址 | 内容 | |
---|---|---|---|
ds | 0000H | data段 |
21个表示年份的字符串,每个字符串占4个字节 共84字节 |
0054H | 21个dword型数据,表示总收入 共84个字节 |
||
00A8H | 21个word型数据,表示雇员人数 共42字节 |
||
ss | 0000H | stack段 | 16个字节 |
es | 0000H | table段 | 21*16个字节 |
cs | 0000H | code段 | 指令入口 |
table段可以看作二维数组结构,每一行(每一年)看作一个结构体型数据,行号可以用[bx]标识。然后用[idata]标识每一个数据项,再用[si]标识数据项中的每一个元素。
要设置data段对应的ds,还要设置栈段对应的SS及SP,以及table段对应的es。代码如下。
code segment
start:
mov ax,data ;设置ds
mov ds,ax
mov ax,stack ;设置栈顶
mov ss,ax
mov sp,10H
add ax,1H ;设置es
mov es,ax
;关于这个es的设置
;ss指向的stack栈段占16个字节,也就是10H字节。
;假设stack段的地址是1000:0000,那物理地址就是10000H。
;那么es指向的table段,物理地址就应该在10010H。
;现在设置es=ss+1=1000+1=1001,
;所以es:0000指向1001:0000,物理地址为 10010H。
code ends
每一年要复制的数据项有多个,不妨先从简单的入手,先复制21年的“年份”数据到table段中。
采用的方法是,内外循环,外层循环指示年份,内层循环指示一个年份字符串中的字节索引。
代码如下。
code segment
start:
;设置好ds ss sp es
mov bx,0 ;第几个年份,索引
mov di,0 ;当前年份字符串中的第几个字节数据,索引
mov cx,21 ;外循环次数,初始值
s0: ;外循环
push cx ;将外循环的循环次数压入栈
mov si,0 ;data段中第几个字节数据,索引
mov cx,4 ;设置内循环的循环次数
s: ;内循环
mov al,ds:[di] ;复制数据
mov es:[bx+si],al
inc si
inc di
loop s ;内循环结束
add bx,10H ;bx定位到table中下一年的数据
pop cx ;pop出外循环的循环次数
loop s0 ;外循环结束
mov ax,4c00H ;程序返回
int 21H
code ends
这与复制“年份”数据几乎是一样的,只需要改动很少的代码就可以(只要把si的值从0改为5就可以)。
mov bx,0
mov si,5 ;这里要修改
mov cx,21
s1:
push cx
mov cx,4
s9:
mov al,ds:[di]
mov es:[bx+si],al
inc si
inc di
loop s9
add bx,10H
mov si,5 ;这里也要修改
pop cx
loop s1
这部分的代码仍然与上边的很相似,要改的地方除了si的值之外,还有内循环的次数cx要改为2次,因为“雇员人数”占的是2个字节。
mov bx,0
mov si,0aH ;这里要修改
mov cx,21
s2:
push cx
mov cx,2 ;内循环次数为2
s99:
mov al,ds:[di]
mov es:[bx+si],al
inc si
inc di
loop s99
add bx,10H
mov si,0aH ;这里也修改
pop cx
loop s2
这需要用到除法计算(div指令),被除数是年总收入,32位,除数是雇员人数,16位。因此,被除数的低16位用ax存储,高16位用dx存储。运算结果的商存储在ax中,余数存储在dx中。
mov bx,0
mov si,0dH ;标记运算结果应写在哪个位置
mov cx,21
s3:
mov ax,es:[bx+5] ;将年总收入低8位存入ax
mov dx,es:[bx+7] ;将年总收入高8位存入dx
div word ptr es:[bx+0aH] ;除法运算
mov es:[bx+si],ax ;将运算结果的商存入内存
add bx,10H ;bx指向下一年
loop s3
综上,得到以下代码,能够实现题目要求。
assume cs:code,ds:data,ss:stack
data segment
db '1975','1976','1977','1978','1979','1980','1981','1982','1983','1984'
db '1985','1986','1987','1988','1989','1990','1991','1992','1993','1994','1995'
dd 16,22,382,1356,2390,8000,16000,24486,50065,97479,140417,197514
dd 345980,590827,803530,1183000,1843000,2759000,3753000,4649000,5937000
dw 3,7,9,13,28,38,130,220,476,778,1001,1442,2258,2793,4037,5635,8226
dw 11542,14430,15257,17800
data ends
stack segment
db 0
stack ends
table segment
db 21 dup ('year summ ne ?? ')
table ends
code segment
start:
mov ax,data ;段寄存器设置
mov ds,ax
mov ax,stack
mov ss,ax
mov sp,10H
add ax,1H
mov es,ax
mov bx,0 ;复制 年份
mov di,0
mov cx,21
s0:
push cx
mov si,0
mov cx,4
s:
mov al,ds:[di]
mov es:[bx+si],al
inc si
inc di
loop s
add bx,10H
pop cx
loop s0
mov bx,0 ;复制 年总收入
mov si,5
mov cx,21
s1:
push cx
mov cx,4
s9:
mov al,ds:[di]
mov es:[bx+si],al
inc si
inc di
loop s9
add bx,10H
mov si,5
pop cx
loop s1
mov bx,0 ;复制 雇员人数
mov si,0aH
mov cx,21
s2:
push cx
mov cx,2
s99:
mov al,ds:[di]
mov es:[bx+si],al
inc si
inc di
loop s99
add bx,10H
mov si,0aH
pop cx
loop s2
mov bx,0 ;计算 人均收入
mov si,0dH
mov cx,21
s3:
mov ax,es:[bx+5]
mov dx,es:[bx+7]
div word ptr es:[bx+0aH]
mov es:[bx+si],ax
add bx,10H
loop s3
mov ax,4c00H ;程序返回
int 21H
code ends
end start
观察发现,有三段用于复制数据的代码相似度很高,作为热爱编程的人,这实在难以忍受。于是仿照高级编程语言的函数概念,在code段开头先写一段函数(不过没有函数体封装),然后再写start部分。这样,三段复制数据的程序,就可以通过jmp指令调用code段开头的函数了。而函数每次执行完,要跳转到正确的位置继续执行(不然容易出现死循环),这里用dx寄存器存储要跳转的主程序位置的偏移地址。
同时,函数中执行循环时,内循环次数有时是4,有时是2,这就不能固定,要用变量来存储。于是我就用bp寄存器来存储,在函数中用到的时候就mov cx,bp取出来。原本觉得这个数据应该暂存到内存中,但是想到只有一个栈,还要多次考虑push 与 pop的顺序问题(毕竟还有一个外循环次数要存到栈中),比较麻烦。加上这个简单的小程序中,寄存器的数量还能够用,于是就直接用寄存器来解决了。
以下就是用函数思想优化之后的部分代码。
code segment
mov bx,0 ;相当于一个函数
mov cx,21
s0:
push cx
mov cx,bp ;bp存储内循环的循环次数
s:
mov al,ds:[di] ;将对应的数据从data段复制到table段
mov es:[bx+si],al
inc si
inc di
loop s
add bx,10H
sub si,bp
pop cx
loop s0 ;外循环执行21次,复制21年的数据
jmp dx ;dx存储有接下来应该执行的指令的地址
; 以上相当于函数部分,以下是程序入口
start:
mov ax,data ;设置段地址
mov ds,ax
mov ax,stack
mov ss,ax
mov sp,10H
add ax,1H
mov es,ax
mov di,0 ;复制 年份 数据
mov si,0
mov dx,3FH
mov bp,4
mov ax,0H
jmp ax
mov si,5 ;复制 年总收入 数据
mov dx,4DH
mov bp,4
mov ax,0H
jmp ax
mov si,0aH ;复制 雇员人数 数据
mov dx,4BH
mov bp,2 ;雇员人数占2字节,所以复制2次
mov ax,0H
jmp ax
mov ax,4c00H
int 21H
code ends
assume cs:code,ds:data,ss:stack
data segment
db '1975','1976','1977','1978','1979','1980','1981','1982','1983','1984'
db '1985','1986','1987','1988','1989','1990','1991','1992','1993','1994','1995'
dd 16,22,382,1356,2390,8000,16000,24486,50065,97479,140417,197514
dd 345980,590827,803530,1183000,1843000,2759000,3753000,4649000,5937000
dw 3,7,9,13,28,38,130,220,476,778,1001,1442,2258,2793,4037,5635,8226
dw 11542,14430,15257,17800
data ends
stack segment
db 0
stack ends
table segment
db 21 dup ('year summ ne ?? ')
table ends
code segment
mov bx,0 ;一段函数
mov cx,21
s0: ;外循环,共循环21次
push cx
mov cx,bp ;bp即内循环的次数
s:
mov al,ds:[di] ;将对应的数据项从data段复制到table段
mov es:[bx+si],al
inc si
inc di
loop s
add bx,10H ;bx定位到下一年
sub si,bp ;将si恢复到数据复制过程之前的值
pop cx
loop s0
jmp dx ;返回到调用者的下一句代码
;****************以上相当于一段函数
start:
mov ax,data ;设置段地址
mov ds,ax
mov ax,stack
mov ss,ax
mov sp,10H
add ax,1H
mov es,ax
mov di,0 ;复制 年份
mov si,0
mov dx,3FH
mov bp,4
mov ax,0H
jmp ax
mov si,5 ;复制 年总收入
mov dx,4DH
mov bp,4
mov ax,0H
jmp ax
mov si,0aH ;复制 雇员人数
mov dx,5BH
mov bp,2
mov ax,0H
jmp ax
mov bx,0 ;计算 人均收入
mov si,0dH
mov cx,21
s3:
mov ax,es:[bx+5]
mov dx,es:[bx+7]
div word ptr es:[bx+0aH]
mov es:[bx+si],ax
add bx,10H
loop s3
mov ax,4c00H ;程序返回 debug时可用 g cs:0078 跳到这一句
int 21H
code ends
end start
在dos界面中使用 d es:0 命令查看程序运行效果,如图。
注意,题目给的data段数据中,只有年份是字符串,其它数据不是字符串;而用d 命令显示内存内容的时候,是将内存内容转为ASCII码字符串进行显示。所以效果图中,也只有年份数据能一目了然。
这是目前写的最长的汇编程序了。加油!
本文是王爽老师《汇编语言》(第四版)第八章 实验7 寻址方式再结构化数据访问中的应用 的分析及代码。