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


    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

  • 相关阅读:
    在zend framework框架中try{}catch(Exception e){}的跳转问题
    【上】安全HTTPS-全面具体解释对称加密,非对称加密,数字签名,数字证书和HTTPS
    iOS 图像处理-剪裁图像
    Delphi DBGrid记录全选和反选拖动处理
    在DbGrid中,不按下Ctrl,单击鼠标如何实现多选?谢谢
    在DBGrid中实现多选功能
    回车跳到下一个EDIT
    远程控制篇:用Delphi模拟键盘输入/鼠标点击
    SQL的拼接语句在DELPHI中怎么写
    Delphi DbGridEh实现表格没有内容的渐变效果
  • 原文地址:https://www.cnblogs.com/poli/p/4659392.html
Copyright © 2020-2023  润新知