zoukankan      html  css  js  c++  java
  • 汇编学习笔记(6) -- 更灵活的定位内存地址的方法

     
    and 和 or指令
    and指令:逻辑与指令,按位进行与运算。
        mov al,01100011B
        and al,00111011B
    执行后 al = 00100011B
    0 1 1 0 0 0 1 1
    0 0 1 1 1 0 1 1
    -------------------
    0 0 1 0 0 0 1 1
     
    上下两个同时为1时,结果才为1
    将al的第6位设为0的指令是: and al,10111111B
    将al的第7位设为0的指令是: and al,01111111B
    将al的第0位设为0的指令是: and al,11111110B
     
    or指令:逻辑或指令,按位进行或运算
        mov al,01100011B
        or al, 00111011B
    执行后: al= 01111011B
    0 1 1 0 0 0 1 1
    0 0 1 1 1 0 1 1
    -------------------
    0 1 1 1 1 0 1 1
    上下两个只要一个为1,结果就为1
     
    将al的第6位设为1的指令是: or al,01000000B
    将al的第7位设为1的指令是: or al,10000000B
    将al的第0位设为1的指令是: or al,0000001B
     
     
     
    关于ASCII码
    世界上有很多编码方案,有种方案叫做ASCII编码,是在计算机系统中通常被采用的
    简单地说,所谓编码方案,就是一套规则,它约定了用什么样的信息来表示现实对象
     
    比如说,在ASCII编码方案中,用61H表示“a”,62H表示“b”
     
    一个文本编辑过程中,就包含着按照ASCII编码规则进行的编码和解码。
    在文本编辑过程中,我们按下键盘的a键,就会在屏幕上看到“a”。这是怎样一个过程呢?
     
    我们按下键盘的a键,这个按键的信息被送入计算机
    计算机用ASCII 码的规则对其进行编码
    将其转化为61H存储在内存的指定空间中;
    文本编辑软件从内存中取出61H,将其送到显卡上的显存中
    工作在文本模式下的显卡,用ASCII码的规则解释显存中的内容
    61H被当作字符“a”,显卡驱动显示器,将字符“a” 的图像画在屏幕上
     
    我们可以看到,显卡在处理文本信息的时候,是按照ASCII码的规则进行的
    这也就是说,如果我们要想在显示器上看到“a",就要给显卡提供“a”的ASCII 码,61H
    如何提供?当然是写入显存中
     
     
    以字符形式给出的数据
    assume cs:code,ds:data
    data segment
        db 'unIX '
        db'foRK'
    data ends
    
    code segment
        start :
        mov al,'a'
        mov bl, 'b'
        
        mov ax, 4c00h
        int 21h
    code ends
    end start
    上面的源程序中:
    db 'unIX' 相当于 db 75H,6EH,49H,58H
    “u”、 “n”、“I”、 .“X”的ASCII码分别为75H、6EH、49H、58H
     
    db 'foRK' 相当于 db 66H,6FH, 52H, 4BH”
    “f”、“o”、 “R、“K”的ASCII码分别为66H、6FH、52H、4BH
     
    mov al, 'a' 相当于 mov al,61H ,“a” 的ASCII码为61H
    mov bl, 'b' 相当于 mov al,62H ,“b” 的ASCII码为62H
         
     
     
    大小写转换问题
    例题:两个字符串,第一个字符串转换为大写,第二个转换为小写
    assume Cs: codesg,ds :datasg 
        datasg segment
        db 'BaSiC'
        db 'iNfOrMaTiOn'
    datasg ends
     
    分析一波
     
    大写字母的ASCII码比小写字母的 小20H
    大写字母ASCII码 + 20H 就变小写字母
    小写字母ASCII码 - 20H 就变大写字母
     
    但是两个字符串中都是大小写混合
    以字符串一为例 (转换为大写)
    应该是只将小写字母的 ASCII码值 减少20H
    大写字母的不改变
    这就需要程序进行判断,然而我们现在不会
     
    再次观察一下
    会发现除第五位(从右向左数,从零开始, 或者说,是从左到右的第三个)外
    其余位数的二进制数都一样
     
    而大写字母第五位为0 ,小写字母第五位为1
    可以使用or 和 and 修改第五位
    assume cs: codesg,ds:datasg
    datasg segment
        db 'BaSiC'
        db 'iNfOrMaTiOn'
    datasg ends
    
    codesg segment
        start: 
        mov ax, datasg
        mov ds, ax        ;设置ds指向datasg段
        
        mov bx,0        ;设置(bx)=0,ds :bx指向'BaSiC'的第-一个字母
        mov cx,5        ;设置循环次数5,因为'BaSiC'有5个字母
        
        s:
        mov al,[bx]         ;将ASCII码从ds:bx所指向的单元中取出    
        and al,11011111B     ;将al中的ASCII码的第5位置为0,变为大写字母
        mov [bx],al            ;将转变后的ASCII码写回原单元
        inc bx                ;(bx)加1,ds:bx指向下一个字母
        loop s
        
        mov bx,5        ;设置(bx) =5, ds: bx指向'iNfOrMaTiOn'的第-一个字母
        mov cx,11        ;设置循环次数11,因为' iNfOrMaTiOn'有11个字母
        
        s0:mov al,[bx]
        or al, 00100000B    ;将al中的ASCII码的第5位置为1,变为小写字母
        mov [bx],al
        inc bx
        loop s0
        
        mov ax, 4c00h
        int 21h
    codesg ends
    end start
     
     
     
     
    [ba+idata]
    在前面,我们用[bx]的方式来指明一个内存单元
    还可以用一种更为灵活的方式来指明内存单元:
    [bx+idata]
    表示一个内存单元,它的偏移地址为(bx)+idata(bx中的数值加,上idata)
     
    指令mov ax,[bx+200]的含义:
    将一个内存单元的内容送入ax,这个内存单元的长度为2个字节(字单元),存放一个字
    偏移地址为bx中的数值加上200,段地址在ds中
    数学化的描述为: (ax)=((ds)* 16+(bx)+200)
     
    该指令也可以写成如下格式(常用):
    mov ax, [200+bx]
    mov ax, 200[bx]
    movax, [bx].200
     
     
    题目:
    用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]
    思考后看分析
     
     
    分析一波:
    mov ax, [bx]
    [bx] = ds:bx 即指向2000:1000内存单元
    但该单元是一个字节
    ax是存储一个字(即两个字节)的
    所以ax值为 00BE
    不理解的可以参考
     
    mov cx, [bx+1]
    [bx] = ds:(bx+1) 指向2000:1001内存单元
    (cx)=0600H
     
    add cx, [bx+2]
    [bx] = ds:(bx+2) 指向2000:1002内存单元
    (cx)=0606H。
     
     
     
     
    用[bx+idata]的方式进行数组的处理
    有了[bx+idata]这种表示内存单元的方式,我们就可以用更高级的结构来看待所要处理的数据
     
     
     
    我们通过下面的问题来理解这一点。
    assume cs: codesg,ds:datasg
    datasg segment
        db 'BaSiC'
        db 'iNfOrMaTiOn'
    datasg ends
    
    codesg segment
        start:
            mov ax,datasg    ;让ax获取datasg段的 段地址
        mov ds,ax    
        mov bx,0        ;初始化bx
        
        mov cx,5        ;循环5次
        s:
        mov al,[bx]    
        and al,1101111b
        mov [bx],al        ;定位第一个字符串中的字符
        
        mov al,[5+bx]
        or al,01000110b  
        mov [5+bx],al    ;定位第二个字符串中的字符
     
        mov al,[10+bx]
        or al,00100000b  
        mov [10+bx],al  ;定位第二个字符串第六个字符
        
        inc bx
        loop s
        
        mov ax, 4c00h
        int 21h
    codesg ends
    end start

     

     
     
     
     
    SI 和 DI 寄存器
    si 和 di是8086CPU中和bx功能相近的寄存器
    si和di不能够分成两个8位寄存器来使用
     
    下面的3组指令实现了相同的功能
    (1) mov bx, 0 mov ax,[bx]
    (2) mov si, 0 mov ax,[si]
    (3) mov di, 0 mov ax,[di]
     
     
    下面的3组指令也实现了相同的功能
    (1) mov bx, 0 mov ax, [bx+123]
    (2) mov si, 0 mov ax, [si+123]
    (3) mov di,0 mov ax, [di+123]
     
     
     
     
    用si和di实现将字符串'welcome to masm!复制到它后面的数据区中。
    assume cs:codesg,ds:datasg
    datasg segment
        db 'welcome to masm!'
        db '...................................'
    datasg ends
    
    codesg segment
        start:
            mov ax,datasg
        mov ds,ax
        mov si,0
        mov di,16
        
        mov cx,8        ;因为一次性转移16位()两个字节),所以只需循环8次
        s:
        mov ax,[si]
        mov [di],ax     ;注意,[bx] [si] [di] 之间不能直接mov  bx,si,di可以
        add si,2        ;因为这两个存储器没有高低位,一次性转移16位,两个字节
        add di,2        ;再加上内存单元是8个字节,所以要+2
        loop s
        
        mov ax,4c00h
        int 21h
    codesg ends
    end start
     
     
    使用更少的代码来实现同样的效果
    提示,可以使用[bx(si或di) + idata] 的方式
     
     
    assume cs:codesg,ds:datasg
    datasg segment
            db 'welcome to masm!'
            db '...................................'
    datasg ends
    
    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] 和 [bx+di]
    在前面,我们用[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]
    将 段地址为 ds,偏移地址 为 bx + si 的内存单元中的 数据送入 ax中
     
    数学化的描述为: (ax)=((ds)* 16+(bx)+(si))
    该指令也可以写成如下格式(常用):
    mov ax, [bx] [si]
     
     
    例题
    内存:
    2000:1000 BE 00 06 00 00 00 ...
    写出下面程序 执行后 ax,bx,cx 中的内容
    mov ax,2000H
    mov ds,ax
    mov bx,1000H
    mov si,0
    mov ax,[bx+si]
    inc si
    mov cx,[bx+si]
    inc si
    mov di,si
    add cx,[bx+di]
     
     
    分析:
    mov ax,2000H ax = 2000
    mov ds,ax ds = 2000
    mov bx,1000H bs = 1000
    mov si,0 si = 0000
    mov ax,[bx+si] ax = 00BE
    inc si si = 0001
    mov cx,[bx+si] cs = 0600
    inc si si = 0002
    mov di,si di = 0002
    add cx,[bx+di] cx = 0606
     
     
     
     
     
    [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]的含义如下:
    将一个内存单元的内容送入ax, 这个内存单元的长度为2字节(字单元),存放一个
    字,偏移地址为bx中的数值加上si中的数值再加上idata,段地址在ds中。
    数学化的描述为: (ax)=((ds)* 16+(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
     
     
    例题
    内存中:
    2000:1000 BE 00 06 00 6A 22 .....
    写出下面程序 执行后 ax,bx,cx 中的内容
    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
    分析:
    mov ax,2000H ax = 2000
    mov ds,ax ds = 2000
    mov bx,1000H bx = 1000
    mov si,0 si = 0
    mov ax,[bx+2+si] ax = 00BE
    inc si si = 1
    mov cx,[bx+2+si] cx = 6A00
    inc si si = 2
    mov di,si di = 2
    mov bx,[bx+2+di] bx = 226A
     
     
     
    不同寻址方式的灵活应用
    如果我们比较一下前面用到的几种定位内存地址的方法(可称为寻址方式)
    就可以发现:
    (1) [idata]用一个 常量 来表示地址,可用于直接定位一个内存单元;
    (2) [bx]用一个 变量 来表示内存地址,可用于间接定位一个内存单元;
    (3)[bx+idata]用一个 变量 和 常量 表示地址,可在一个起始地址的基础上用 变量 间接定位一个内存单元;
    (4) [bx+si]用两个 变量 表示地址;
    (5) [bx+si+idata]用两个 变量 和一个 常量 表示地址。
     
     
    可以看到,从[idata]一 直到[bx+si+idata]
    我们可以用更加灵活的方式来定位一个内存单元的地址
     
    这使我们可以从更加结构化的角度来看待所要处理的数据。
    assume cs:codesg,ds:datasg
    datasg segment
            db '1. file.........'  ;这里设置的字符串长为16! .是为了更清晰
            db '2. edit.........'
            db '3. search.......'
            db '4. view.........'
            db '5. options......'
            db '6. help.........'
    datasg ends
    codesg segment
    start:
    codesg ends
    end start

     
     
    答案
    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段中每个单词改为大写字母
    assume cs:codesg,ds:datasg
    datasg segment
        db 'ibm.............'  一个字符串,十六个字节
        db 'dec.............'
        db 'dos.............'
        db 'vax.............'
    datasg ends
    
    codesg segment
        start :    
    codesg ends
    end start
     
    分析:
    我们需要进行4x3次的二重循环,用变量R定位行,变量C定位列
    外层循环按行来进行,内层按列来进行
    首先用R定位第1行,然后循环修改R行的前3列
    然后再用R定位到下一行,再次循环修改R行的前3列.....
    如此重复直到所有的数据修改完毕
    处理的过程大致如下
     
    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
        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
    
        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 '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,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
     
     
    上面的程序用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 '1. file.........'
            db '2. edit.........'
            db '3. search.......'
            db '4. view.........'
            db '5. options......'
            db '6. help.........'
    datasg ends
    
    stack segment
        dw 0,0,0,0,0,0,0,0
    stack 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
    (076c:0000那一行是栈)
     
     
     参考: 王爽 - 汇编语言 和 小甲鱼零基础汇编
     
     

  • 相关阅读:
    STM32F2系列系统时钟默认配置
    在电源上叠加一个脉冲信号,模拟一个干扰信号
    const用法
    指向指针的指针
    Judge Route Circle
    汉明距离
    绘制三角形
    OpenGL工作流程
    OpenGL环境搭建
    next()方法 执行下一个中间件 类似than
  • 原文地址:https://www.cnblogs.com/ZhouJiaHao/p/13703054.html
Copyright © 2011-2022 走看看