汇编语言不同的寻址方式的灵活应用

<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)

511遇见

我们需要进行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所示

511遇见

在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所示

511遇见

在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指令。


发布日期:

所属分类: 易语言 标签: