蒙珣的博客

活好当下,做好今天该做的事情。

0%

实验6 寻址方式在结构化数据访问中的应用

实验6

编程,将 datasg 段中每个单词的头一个字母变成大写字母

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
assume cs:codesg, ds:datasg

datasg segment
db '1. file '
db '2. edit '
db '3. search '
db '4. view '
db '5. options '
db '6. help '
datasg ends

codesg segment
start:
codesg ends

end start

分析:

datasg 中的数据的存储结构

我们可以看到,在 datasg 中定义了6个字符串,每个长度为16个字节(注意,为了直观,每个字符串后面都加上了空格符,以使它们的长度刚好为16个字节)。因为它们是连续存放的,可以将这6个字符串看成一个6行16列的二维数组。按要求,需要修改每个单词的第一个字母,即二维数组的每一行的第4列(相对于行首的偏移地址为3)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
assume cs:codesg, ds:datasg

datasg segment
db '1. file '
db '2. edit '
db '3. search '
db '4. view '
db '5. options '
db '6. help '
datasg ends

codesg segment

start: mov ax,datasg
mov ds,ax
mov bx,0
mov cx,6

s0: mov al,[bx+3]
and al,11011111b
mov [bx+3],al
add bx,16
loop s0

codesg ends

end start

编程,将 datasg 段中每个单词改为大写字母

1
2
3
4
5
6
7
8
9
10
11
12
13
14
assume cs:codesg,ds:datasg

datasg segment
db 'ibm '
db 'dec '
db 'dos '
db 'vax '
datasg ends

codesg segment
start:
codesg ends

end start

分析:

datasg 中的数据的存储结构如图

我们需要进行 4X3次的二重循环,用变量R定位,变量C定位列。外层循环按行来进行,内层按列来进行。首先用R定位第1行,然后循环修改R行的前3列;然后再用R定位到下一行,再次循环修改R行的前3列……,如此重复直到所有的数据修改完毕,处理的过程大致如下。

1
2
3
4
5
6
7
8
9
10
11
	R=第一行的地址:
mov cx,4

s0: C=第一列的地址
mov cx,3

s: 改变R行,c列的字母为大写
C=下一列的地址:
loop s
R=下一行的地址
loop s0

我们用 bx 来作变量,定位每行的岂是地址,用 si 定位要修改的列,用 [bx+si] 的方式来对目标单元进行寻址,程序如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
	mov ax,datasg
mov ds,ax
mov bx,0
mov cx,4

s0: mov si,0
mov cx,3

s: mov al,[bx+si]
and al,11011111b
mov [bx+si],al
inc si

loop s

add bx,16
loop s0

仔细阅读上面的程序,看看有什么问题?

思考后看分析

分析

问题在于cx的使用,我们进行二重循环,却只用了一个循环计数器,造成在进行内循环的时候,覆盖了外层循环的循环计数值。多用一个计数器又不可能,因为loop指令默认cx为循环计数器。怎么办呢?

我们应该在每次开始内层循环的时候,将外层循环的cx中的数值保存起来,在执行外层循环的loop指令前,再恢复外层循环的cx数值。可以用寄存器dx来临时保存cx中的数值,改进程序如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
    mov ax,datasg
mov ds,ax
mov bx,0
mov cx,4
mov dx,cx

s0: mov dx,cx
mov si,0
mov cx,3

s: mov al,[bx+si]
add al,11011111b
mov [bx+si],al
inc si
loop s

add bx,16
mov cx,dx
loop s0

上面的程序用dx来暂时存放cx中的值,如果在内循环中,dx寄存器也被使用,该怎么办?我们似乎可以使用别的寄存器,但是CPU中的寄存器数量毕竟是有限的,如8086CPU只有14个寄存器。在上面的程序中,si、cx、ax、bx,显然不能用来暂存cx中的值,因为这些寄存器在循环中也要使用;cs、ip、ds也不能用,因为cs:ip时刻指向当前指令,ds指向datasg段;可用的就只有:dx、di、es、ss、sp、bp等6个寄存器了。可是如果循环中的程序比较复杂,这些寄存器也都被使用的话,那么该如何?

我们在这里讨论的问题是,程序中经常需要进行数据的暂存,怎么样做才更合理。这些数据可能是寄存器中的,也可能是内存中的。我们可以用寄存器暂存它们,但是这不是一个一般化的解决方案,因为寄存器的数量是有限的,每个程序中可使用的寄存器都不一样。我们希望寻找一个通用的方案,来解决这种编程中经常出现的问题。

显然,我们不能选择寄存器,那么可以使用的就是内存了。可以考虑将需要暂存数据放到内存单元中,需要使用的时候,再从内存单元中恢复。这样我们就需要开辟一段内存空间。再次改进的程序如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
assume cs:code,ds:datasg

datasg segment

db 'ibm '
db 'dec '
db 'dos '
db 'vax '
dw 0 ;定义一个字,用来暂存cx

datasg ends

codesg segment

start: mov ax,datasg
mov ds,ax
mov bx,0
mov cx,4

s0: mov ds:[40H],cx ;将外层循环的cx值保存在datasg:40H单元中
mov si,0
mov cx,3 ;cx设置为内层循环次数

s: mov al,[bx+si]
and al,11011111b
mov [bx+si],al
inc si
loop s

add bx,16
mov cx,ds:[40H] ;用datasg:40H单元中的值恢复cx
loop s0 ;外层循环的loop指令将cx中的计数值减1

mov ax,4c00H
int 21H

codesg ends

end start

上面的程序中,用内存单元来保存数据,可是上面的做法却有些麻烦,因为如果需要保存多个数据的时候,你必须要记住数据放到哪个单元中,这样程序很容易乱。

我们使用内存来暂存数据,这一点是确定了的,但是值得推敲的是,我们用怎样的结构来保存这些数据,而使得我们的程序更加清晰。一般来说,在需要存放数据的时候,我们都应该使用栈。回忆一下,栈空间在内存中,采用相关的指令,如 push、pop 等,可对其进行特殊的操作。下面,再次改进我们的程序。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
assume cs:code,ds:datasg

datasg segment
db 'ibm '
db 'dec '
db 'dos '
db 'vax '
datasg ends

stacksg segment ;定义一个段,用来做栈段,容量为16个字节
dw 0,0,0,0,0,0,0,0
stacksg ends

codesg segment

start: mov ax,stacksg
mov ss,ax
mov sp,16
mov ax,datasg
mov ds,ax
mov bx,0
mov cx,4

s0: push cx ;将外层循环的cx值压栈
mov si,0
mov cx,3 ;cx设置为内层循环的次数

s: mov al,[bx+si]
and al,11011111b
mov [bx+si],al
inc si
loop s

add bx,16
pop cx ;从栈顶弹出原cx的值,恢复cx
loop s0 ;外层循环的loop指令将cx中的计数值减1

mov ax,4c00H
int 21H

codesg ends

end start

编程,将 datasg 段中每个单词的前4个字母改为大写字母

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
assume cs:codesg,ss:stacksg,ds:datasg

stacksg segment
dw 0,0,0,0,0,0,0,0
stacksg ends

datasg segment
db '1. display '
db '2. brows '
db '3. replace '
db '4. modify '
datasg ends

codesg segment
start:
codesg ends

end start

分析

datasg 中的数据的存储结构

在datasg中定义了4个字符串,每个长度为16个字节(注意,为了是使我们在Debug中可以直观地查看,每个字符串的后面都加上了空格,以使它们的长度刚好为16个字节)。因为它们是连续存放的,我们可以将这4个字符串看成一个4行16列的二维数组,按照要求,我们需要修改每个单词的前4个字母,即二维数组的每一行的3~6列

我们需要进行4x4次的二重循环,用变量R定位行,常量3定位每行要修改的起始列,变量C定位相对于起始列的要修改的列。外层循环按行来进行,内层按列来进行。我们首先用R定位第1行,循环修改R行的3+C(0<=C<=3)列;然后再用R定位到下一行,再次修改R行的3+C(0<=C<=3)列……,如此重复直到所有的数据修改完毕。处理过程大致如下。

​ R=第一行的地址:

​ mov cx,4

s0: C=第一个要修改的列相对起始列的地址

​ mov cx,4

s: 改变R行,3+C列的字母为大写

​ C=下一个要修改的列相对于起始列的地址

​ loop s

​ R=下一行的地址

​ loop s0

我们用 bx 作为变量,定位每行的起始地址,用 si 定位要修改的列,用[bx+3+si]的方式来对目标单元进行寻址。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
assume cs:codesg,ss:stacksg,ds:datasg

stacksg segment

dw 0,0,0,0,0,0,0,0

stacksg ends

datasg segment

db '1. display '
db '2. brows '
db '3. replace '
db '4. modify '

datasg ends

codesg segment

start: mov ax,stacksg
mov ss,ax
mov sp,16
mov ax,datasg
mov ds,ax
mov bx,0
mov cx,4

s: push cx
mov di,3
mov cx,4

s0: mov al,[bx+di]
and al,11011111b
mov [bx+di],al
inc di
loop s0

pop cx
add bx,16
loop s

mov ax,4c00h
int 21

codesg ends

end start

代码的执行过程可参考 实验5