更灵活的定位内存地址的方法

7.1 and和or指令
通过该指令可将操作对象的相应位设为0,其他位不变。

例如:
将al的第6位设为0:and al, 10111111B
将al的第7位设为0:and al, 01111111B
将al的第0位设为0:and al, 11111110B

通过该指令可将操作对象的相应位设为1,其他位不变。

例如:
将al的第6位设为1:and al, 01000000B
将al的第7位设为1:and al, 10000000B
将al的第0位设为1:and al, 00000001B

“db ‘unIX’ ” 相当于“db 75H,6EH,49H,58H”, “u”、 “n”、 “I”、 “X”的ASCII码分别为75H、6EH、49H、58H;
“db ‘foRK’ ” 相当于“db 66H,6FH,52H,4BH”, “u”、 “n”、 “I”、 “X”的ASCII码分别为66H、6FH、52H、4BH;
“mov al,’a’”相当于“mov al,61H”,”a”的ASCII码为61H;
“mov al,’b’”相当于“mov al,62H”,”b”的ASCII码为62H。

通过对比,我们可以看出来,小写字母的ASCII码值比大写字母的ASCII码值大20H 。

这样,我们可以想到,如果将 “a” 的ASCII码值减去20H,就可以得到“A”;如果将“A”的ASCII码值加上20H 就可以得到“a”。

对于字符串 “ iNforMaTIOn ” ,我们应只对其中的大写字母所对应的ASCII码进行加20H 的处理,将其转为小写;
而对于其中的小写字母不进行改变,这里面就存在着一个前提,程序必须要能够判断一个字母是大写还是小写。
assume cs:codesg,ds:datasg
datasg segment
db 'BaSiC'
db 'iNfOrMaTiOn'
datasg ends
codesg segment
start: mov ax,datasg
mov ds,ax
mov bx,0
mov cx,5
s: mov al,[bx]
如果(al)>61H,则为小写字母ASCII码,则:sub al,20H
mov [bx],al
inc bx
loop s

可以看出,就ASCII码的二进制形式来看,除第5位(位数从0开始计算)外,大写字母和小写字母的其他各位都一样。

大写字母ASCII码的第5位(位数从0开始计算)为0,小写字母的第5位为1。

这样,我们就有了新的方法:

一个字母,我们不管它原来是大写还是小写:
我们将它的第5位置0,它就必将变为大写字母; and [bx],11101111B
将它的第5 位置1,它就必将变为小写字母 or [bx],00010000B

7.5 [bx+idata]
在前面,我们可以用[bx]的方式来指明一个内存单元, 我们还可以用一种更为灵活的方式来指明内存单元:

[bx+idata]表示一个内存单元,它的偏移地址为(bx)+idata(bx中的数值加上idata)。
我们看一下指令mov ax,[bx+200]的含义:

将一个内存单元的内容送入ax,这个内存单元的长度为2字节(字单元),存放一个字,偏移地址为bx中的数值加上200,段地址在ds中。、

数学化的描述为: (ax)=((ds)*16+(bx)+200)

指令mov ax,[bx+200]也可以写成如下格式(常用):
mov ax,[200+bx]

mov ax,200[bx]

mov ax,[bx].200

问题7.1
用Debug查看内存,结果如下:
2000:1000 BE 00 06 00 00 00 ……
写出下面的程序执行后,ax、bx、cx中的内容。

mov ax,2000H
mov ds,ax
mov bx,1000H
mov ax,[bx]
mov cx,[bx+1]
add  cx,[bx+2]

7.6 用[bx+idata]的方式进行数组的处理
我们改进后的程序:
mov ax,datasg
mov ds,ax
mov bx,0

mov cx,5

s: mov al,[bx] ;定位第一个字符串的字符
and al,11011111b
mov [bx],al
mov al,[5+bx] ;定位第二个字符串的字符
or al,00100000b
mov [5+bx],al
inc bx
loop s

char a[5]=“BaSiC”;
char b[5]=“MinIX”;
main()
{
int i;
i=0;
do
{
a[i]=a[i]&0xDF;
b[i]=b[i]&0x20;
i++;
}while(i<5);
}

如果读者熟悉C语言的话,可以比较一下这个C程序和上面的汇编程序的相似之处。尤其注意它们定位字符串中字符的方式:

C语言定位方式:a[i],b[i]
汇编语言定位方式:0[bx],5[bx]

7.7 SI和DI
SI和DI是8086CPU中和bx功能相近的寄存器,但是SI和DI不能够分成两个8 位寄存器来使用。
下面的三组指令实现了相同的功能:

(1) mov bx,0
mov ax,[bx]
(2) mov si,0
mov ax,[si]
(3) mov di,0
mov ax,[di]
下面的三组指令也实现了相同的功能:
(1) mov bx,0
mov ax,[bx+123]
(2) mov si,0
mov ax,[si+123]
(3) mov di,0
mov ax,[di+123]

codesg segment
start: mov ax,datasg
mov ds,ax
mov si,0
mov di,16
mov cx,8
s: mov ax,[si]
mov [di],ax
add si,2
add di,2
loop s

     mov ax,4c00h
     int 21h

codesg ends
end start

我们可以利用[bx(si或di)+idata]的方式,来使程序变得简洁。
codesg segment
start: mov ax,datasg
mov ds,ax
mov si,0
mov cx,8
s: mov ax,0[si]
mov 16[si],ax
add si,2
loop s
mov ax,4c00h
int 21h
codesg ends
end start

在前面,我们用[bx(si或di)]和[bx(si或di)+idata] 的方式来指明一个内存单元,我们还可以用更灵活的方式:
[bx+si]
[bx+di]

[bx+si]和[bx+di]的含义相似,我们以[bx+si]为例进行讲解。

[bx+si]表示一个内存单元,它的偏移地址为(bx)+(si)(即bx中的数值加上si中的数值)。

我们看下指令mov ax,[bx+si]的含义:
将一个内存单元的内容送入ax,这个内存单元的长度为2字节(字单元),存放一个字,偏移地址为bx中的数值加上si中的数值,段地址在ds中。

7.9 [bx+si+idata]和[bx+di+idata]
[bx+si+idata]和[bx+di+idata]的含义相似,我们以[bx+si+idata]为例进行讲解。

[bx+si+idata]表示一个内存单元
它的偏移地址为(bx)+(si)+idata。
(即bx中的数值加上si中的数值再加上idata)

指令mov ax,[bx+si+idata]:
该指令也可以写成如下格式(常用):
mov ax,[bx+200+si]
mov ax,[200+bx+si]
mov ax,200[bx][si]
mov ax,[bx].200[si]
mov ax,[bx][si].200

7.10 不同的寻址方式的灵活应用
如果我们比较一下前面用到的几种定位内存地址的方法(可称为寻址方式),就可以发现有以下几种方式:
(1)[iata] 用一个常量来表示地址,可用于直接定位一个内存单元;
(2)[bx]用一个变量来表示内存地址,可用于间接定位一个内存单元;
(3)[bx+idata] 用一个变量和常量表示地址,可在一个起始地址的基础上用变量间接定位一个内存单元;
(4)[bx+si]用两个变量表示地址;
(5)[bx+si+idata] 用两个变量和一个常量表示地址。

编程,将datasg段中每个单词的头一个字母改为大写字母
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

我们需要进行6次循环,用一个变量R定位行,用常量3 定位列。处理的过程如下:

    BX先存放第一行的地址
    mov cx,6;因为总共有六行
 s: 改变第BX行,第3列的字母为大写
    改变BX的值是它指向下一行的地址
    loop

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

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
s: mov al, [bx+3]     ;注意,单位是字节,所以是al
   and al, 11011111b  ;使第五位为零,这样呢,就确定了大写!
   mov [bx+3], al
   add bx, 16
   loop s

   mov ax, 4c00h
   int 21h

codesg ends
end start

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

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中定义了4个字符串,每个长度为16字节。
(注意,为了使我们在Debug 中可以直观地查看,每个字符串的后面都加上了空格符,以使它们的长度刚好为16byte)

因为它们是连续存放的,我们可以将这 4 个字符串看成一个 4行16列的二维数组。
按照要求,我们需要修改每一个单词,即二维数组的每一行的前3列。

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

处理的过程大致如下:
R=第一行的地址;
mov cx,4
s0: C=第一列的地址
mov cx,3
s: 改变R 行,C列的字母为大写
C=下一列的地址;
loop s
R=下一行的地址
loop s0
完整代码:

assume cs:codesg,ds:datasg

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

codesg segment
start: mov ax,datasg
mov ds,ax
mov bx,0 ;用BX来定位行

   mov cx,4

s0: mov si,0 ;用SI来定位列
mov cx,3
s: mov al,[bx+si]
and al,11011111b
mov [bx+si],al

   inc si

   loop s

   add bx,16
   loop s0

   mov ax,4c00h
   int 21h

codesg ends
end start

问题在于cx的使用,我们进行二重循环,却只用了一个循环计数器,造成在进行内层的时候覆盖了外层循环的循环计数值。

多用一个计数器又不可能,因为loop指令默认cx为循环计数器。
怎么办呢?

我们应该在每次开始内层循环的时候,将外层循环的cx中的数值保存起来,在执行外层循环的loop指令前,再恢复外层循环的cx数值。

我们可以用寄存器dx来临时保存cx中的数值。

看看我们改进后的程序

assume cs:codesg,ds:datasg

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

codesg segment
start: mov ax,datasg
mov ds,ax
mov bx,0 ;用BX来定位行

   mov cx,4

s0: mov dx,cx ;我们用dx寄存器来临时存放外层cx的值
mov si,0 ;用SI来定位列
mov cx,3

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

   inc si

   loop s       ;此时的CX已经为零了,对吧?

   add bx,16
   mov cx,dx   ;在进行外层循环的时候恢复CX的值。
   loop s0     ; CX = CX - 1  再判断是否为零

   mov ax,4c00h
   int 21h

codesg ends
end start

上面的程序用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个寄存器了。

可是如果循环中的程序比较复杂,这些寄存器也都被使用的话,那么该如何?
我们在这里讨论的问题是,程序中经常需要进行数据的暂存,我们怎样做将更为合理。

这些数据可能是寄存器中的,也可能是内存中的。

我们可以用寄存器暂存它们,但是这不是一个一般化的解决方案,因为寄存器的数量有限,每个程序中可使用的寄存器都不一样。

我们希望寻找一个通用的方案,来解决这种在编程中经常会出现的问题。

显然,我们不能选择寄存器,那么可以使用的就是内存了。

我们可以考虑将需要暂存的数据放到内存单元中,需要使用的时候,再从内存单元中恢复。这样我们就需要开辟一段内存空间。

一起来看再次改进的程序

改造后的程序中,用内存单元来保存数据;

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

assume cs:codesg,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

改造后的程序中,用内存单元来保存数据;

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

我们使用内存来暂存数据,这一点是确定了的,但是值得推敲的是,我们用怎样的结构来保存这些数据,而使得我们的程序更加清晰,更容易读懂和被接受?

我们来参考VC编译器的做法!

一般来说,在需要暂存数据的时候,我们都应该使用栈,回忆一下,栈空间在内存中,采用相关的指令,如:push、pop等,可对其进行特殊的操作。

我们将再次改进我们的程序 !

assume cs:codesg,ds:datasg,ss:stacksg

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段中每个单词的前四个字母改为大写字母:
assume cs:codesg,ds:datasg,ss:stacksg
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

写程序:
assume cs:codesg,ds:datasg,ss:stacksg

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

s0: push cx
mov si,0 ;定义列
mov cx,4
s: mov al,[bx+3+si] ;这里是定位到每个要索引的字母,总共每行有四个字母
and al,11011111b ;实现使他变为大写字母,OK?
mov [bx+3+si],al
inc si ;使他指向下一个字母。
loop s

   add bx,16
   pop cx
   loop s0

   mov ax,4c00h
   int 21h

codesg ends
end start

原文地址:https://www.cnblogs.com/poli/p/4659392.html