如果我们比较一下前面用到的几种定位内存地址的方法(可称为寻址方式),就可以发现:
<1> [idata]用一个常量来表示地址,可用于直接定位一个内存单元;
<2> [bx]用一个常量来表示内存地址,可用于间接定位一个内存单元;
<3> [bx+idata]用一个变量和常量表示地址,可在一个起始地址的基础上用变量间接定位一个内存单元;
<4> [bx+si]用两个变量表示地址;
<5> [bx+si+idata]用两个变量和一个常量表示地址。
可以看到,从〔idata〕一直到「bx+s i+idata],我们可以用更加灵活的方式来定位一个内存单元的地址。这使我们可以从更加结构化的角度来看待所要处理的数据。下面我们通过一个问题的系列来体会CPU提供多种一寻址方式的用意,并学习一些相关的编程技巧。
编程,将datasg段中每个单词的头一个字母改为大写字母
;将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: mov ax,datasg mov ds,ax mov bx,0 mov cx,6 s: mov al,[bx +3] and al,11011111B mov [bx + 3],al add bx,16 loop s mov ax,4c00H int 21h codesg ends end start
分析:
datasg中的数据的存储结构,如图7.2所示。
我们可以看到,在datasg中定义了6个字符串,每个长度为16个字节(注意,为了直观,每个字符串的后面都加上了空格符,以使它们的长度刚好为16个字节)。因为它们是连续存放的,可以将这6个字符串看成一个6行16列的二维数组。按照要求,需要修改每一个单词的第一个字母,即二维数组的每一行的第4列(相对于行首的偏移地址为3)
我们需要进行6次循环,用一个变量R定位行,用常量3定位列。处理的过程如下。
R=第一行的地址 mov cx,6 s:改变R行,3列的字母为大写 R=下一行的地址 loop s
我们用bx作变量,定位每行的起始地址,用3定位要修改的列,用[bx+idata]的方式来对目标单元进行寻址,程序如下。
mov ax,datasg mov ds,ax mov bx,0 mov cx,6 s: mov al,[bx +3] and al,11011111B mov [bx + 3],al add bx,16 loop s
编程,将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中的数据的存储结构如图7.3所示
在datasg中定义了4个字符串,每个长度为16个字节(注意,为了使我们在Debug中可以直观地查看,每个字符串的后面都加上了空格符,以使它们的长度刚好为16个字节)。因为它们是连续存放的,我们可以将这4个字符串看成一个4行16列的二维数组。按照要求,我们需要修改每一个单词,即二维数组的每一行的前3列。
我们需要进行4x3次的二重循环,用变量R定位行,变量C定位列。外层循环按行来进行,内层按列来进行。首先用R定位第I行,然后循环修改R行的前3列;然后再用R定位到下一行,再次循环修改R行的前3列……,如此重复直到所有的数据修改完毕。处理的过程大致如下。
R=第一行的地址; mov cx,q s0: C=第一列的地址 mov cx,3 s:改变R行,C列的字母为大写 C=下一列的地址: loop s R=下一行的地址 loop s0
我们用bx来作变量,定位每行的起始地址,用si定位要修改的列,用[bx+si]的方式来对目标单元进行寻址,程序如下。
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 s
仔细阅读上面的程序,看看有什么问题?
问题在于CX的使用,我们进行二重循环,却只用了一个循环计数器,造成在进行内层循环的时候,覆盖了外层循环的循环计数值。多用一个计数器又不可能,因为loop指令默认CX为循环计数器。怎么办呢?
我们应该在每次开始内层循环的时候,将外层循环的CX中的数值保存起来,在执行外层循环的loop指令前,再恢复外层循环的cx数值。可以用寄存器dx来临时保存cx中的数值,改进的程序如下。
mov ax,datasg mov ds,ax mov bx,0 ;行 mov cx,4 s0: mov dx,cx ;将外层循环的cx值保存在dx中 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,dx ;用dx中存放的外层循环的计数值恢复cx loop s0 ;外层循环的loop指令将cx中的计数值减1
上面的程序用dx来暂时存放cx中的值,如果在内层循环中,dx寄存器也被使用,该怎么办?我们似乎可以使用别的寄存器,但是CPU中的寄存器数量毕竞是有限的,如8086CPU只有14个寄存器。在上面的程序中,si, cx, ax, bx,显然不能用来暂存cx中的值,因为这些寄存器在循环中也要使用;cs, ip, ds也不能用,因为cs:ip时刻指向当前指令,ds指向datasg段;可用的就只有:dx, di, es, ss, sp, by等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 dx,cx ;将外层循环的cx值保存在dx中 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,dx ;用dx中存放的外层循环的计数值恢复cx loop s0 ;外层循环的loop指令将cx中的计数值减1 mov ax,4c00H int 21H codesg ends end start
上面的程序中,用内存单元来保存数据,可是上面的作法却有些麻烦,因为如果需要保存多个数据的时候,你必须要记住数据放到了哪个单元中,这样程序容易混乱。
我们使用内存来暂存数据,这一点是确定了的,但是值得推敲的是,我们用怎样的结构来保存这些数据,而使得我们的程序更加清晰。一般来说,在需要暂存数据的时候,我们都应该使用栈。回忆一下,栈空间在内存中,采用相关的指令,如push, pop等,可对其进行特殊的操作。下面,再次改进我们的程序。
;将datasg段中的每个单词修改为大写字母 assume cs:codesg,ds:datasg,ss:stacksg datasg segment db 'ibm ' db 'dec ' db 'doc ' 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个字母改为大写字母
;将datasg段中每个单词的前四个字母改为大写字母 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中的数据的存储结构,如图7.4所示
在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)列……,如此重复直到所有的数据修改完毕。处理的过程大致如下。
;将datasg段中每个单词的前四个字母改为大写字母 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 s0: push cx mov si,0 mov cx,4 s: mov al,ds:[bx + 3 + si] and al,11011111B mov ds:[bx + 3 + si],al inc si loop s pop cx add bx,16 loop s0; mov ax,4c00H int 21h codesg ends end start
一般来讲,在需要暂存数据的时候,我们都应该使用栈.
小结
寻址方式[bx(或si, di)+idata], [bx+si(或di)], [bx+si(或di)+idata]的意义和应用;
二重循环问题的处理:
栈的应用;
大小写转化的方法;
and, or指令。