汇编语言学习记录02之程序设计技巧

位运算技巧在汇编程序中的应用

  • 寄存器初始化

    举例使用:

    xor ax, ax ;将ax寄存器置零
    
  • 判断寄存器是否为0

    假如我们刚刚进行完除法运算,需要判断余数(默认存放在 DX 中)是否为0,我们可以对 DX 自身按位与,触发标志位影响,举例使用如下:

    and dx, dx
    jz next
    
  • 求反的位同 1 异或,维持不变的位同 0 异或

  • 检查某些位是否同时1

    举例:检查 CX 中的第1,6和11位中是否同时为1

    not cx ;先取反,如果所求位是1的话,此时转为0
    test cx, 0842h ;对其他位置0,对所求位同1按位与
    jz yes ;如果结果是0,说明所求位都转变成0了,即所求位原来同时为1
    not cx ;记得取补恢复
    

16进制数转换为ASCII码

DOS功能调用的2号功能的输出,需要传入一个ASCII码的字符。然而我们现在只有一个16进制数,如何得到对应的ASCII码呢?

方法一:查表法

零、适用范围:

适用于只有一位的16进制数(即4位二进制),不适用于两位或两位以上的16进制数。

一、要点:

  • 换码指令—— XLAT:将BX指定的缓冲区中、AL 指定的位移处的一个字节数据取出赋给 AL ,实际相当于 (AL) = (DS:(BX+AL)) 。注意,不是单纯地赋予 AL+BX ,而是对应地址的值。

    换码指令执行前,一般在主存建立一个字节量表格(如下代码4-6行),内含要转换成的目的代码表格首地址存放于 BXAL 存放相对表格首地址的位移量

    换码指令执行后,将 AL 寄存器内容转换为目的代码

二、代码:

assume cs:codesg, ds:datasg

datasg segment
        ASCII   db 30h,31h,32h,33h,34h,35h
                db 36h,37h,38h,39h	   ;0~9的ASCII码
                db 41h,42h,43h,44h,45h,46h ;A~F的ASCII码
        hex     db 09h                     ;任意设定了一个待转换的一位16进制数,这里以'0f'为例
datasg ends

codesg segment
start:
        mov ax, datasg
        mov ds, ax

        mov bx, offset ASCII ;bx存储标号ASCII的偏移地址(将其作为基准值)
        mov al, hex          ;将待转换的16进制数放到低位寄存器
        and al, 0fh          ;按位与,对8位的前4位清0(因为实验只要求输出后4位)
        xlat                 ;换码:al<- DS:[BX+AL] 基准值BX(ASCII的offset)+位移量AL(待转换)
        mov dl, al           ;入口参数:dl<-al
        mov ah, 2            ;02号DOS功能调用
        int 21h              ;显示一个ASCII码字符

        mov ah, 4ch
        int 21h
codesg ends

end start

三、内存中存储的数据:

四、运行结果:

hex赋值 0fh 09h
运行结果

方法二:位运算法

零、适用范围:

适用于多位16进制数,下面演示的是两位16进制数(即8位二进制,1个字节大小,可以存放到 8位寄存器 如 AL等)

一、分析:

我们观察一下 ASCII 码表,发现\(0-9\)的ASCII码(二进制8位)下,前8位具有共同前缀 0011 ,而末8位恰好就是\(0-9\)的二进制表示!那 16 进制下的\(A-F\)呢?我们只需要特判即可,如果该数字的ASCII码不超过 39H (即数字9),那么直接输出即可;若超过(说明数字是A-F),那么直接对其**加上 \(7\) **即可

由于两位16进制数即是8位二进制数,故我们需要先对高4位二进制数进行转换,再对低4位二进制数进行转换。

二、代码:

datasg segment
datasg ends
    
stacks segment
    db 256 dup(?)
stacks ends

codesg segment
    assume cs:codesg, ds:datasg, ss:stacks
start:
    mov ax, datasg
    mov ds, ax
    mov al, 0fah; //将你需要转换的两位16进制放到al寄存器中

    push ax     ;暂存ax
    mov dl, al 
    mov cl, 4   ;用于shr指令,代表移位4位
    shr dl, cl  ;dl先取出al的高4位
    call transASCII
    pop dx      ;将之前存的ax给pop出来
    and dl, 0fh ;dl取出取出al的低4位
    call transASCII

    mov ah, 4ch
    int 21h

;子程序实现转换
transASCII proc far
    or dl, 30h ;对该串的前4位按位或上"0011"(ASCII码中数字的前缀串)
    cmp dl, 39h ;39H是数字9的ASCII码16进制
    jbe output ;小于等于39(即数字没超过9),直接输出
    add dl, 7  ;大于等于39 (即数字为A-F),需要对ASCII码加上7
output:
    mov ah, 2
    int 21h
    ret
transASCII endp

codesg ends
    end start


三、运行结果:

AL赋值 fah 9ch
运行结果

多分支结构——地址表法

一、要点:

  • 当程序分支较多时,可使用地址表(跳转表)来实现程序分支。我们在数据段事先安排一个地址表,里面连续存放的是一系列跳转地址,即各分支程序的入口地址

  • 由于使用 DOS 功能调用的1号功能,输入只能是一个字节大小的字符,同时是以二进制的ASCII码存入 AL 中。故需将 AL 存下 ASCII 码转换为对应的数字。(在下面的第30行代码)

  • ⭐ 因为一个段最大存储空间是 \(64\) KB,偏移地址故使用 16 位表示。因此需要用 2 个字节存储单元 (1个字)去储存偏移地址。对于这个地址表而言,要两个字节、两个字节…如此去寻找表中的每个元素。(在下面的第32行代码)

二、代码:

datasg segment
    str0 db 0dh,0ah,'input number (1-3):',0dH,0ah,'$'
    str1 db 0dh,0ah,'chapter1: introduction',0dH,0ah,'$'
    str2 db 0dh,0ah,'chapter2: designing method',0dH,0ah,'$'
    str3 db 0dh,0ah,'chapter3: experiment',0dH,0ah,'$'
    table dw disp1, disp2, disp3  ;在数据段中定义各个标号的偏移地址,相当于offset disp1,此时相应内存单元即存了(16位)偏移量
datasg ends

stacks segment
    ;
stacks ends

codesg segment
    assume cs:codesg, ds:datasg, ss:stacks
start:
    mov ax, datasg
    mov ds, ax
input:
    mov ah, 09h ;输出字符串
    mov dx, offset str0
    int 21h

    mov ah, 01h
    int 21h
    cmp al, '1'
    jb input ;如果小于(below)1,则重新输入
    cmp al, '3'
    ja input ;如果大于(above)3,则重新输入
    
    and ax, 000fh; //16位ax只保留最后4位,从而实现ASCII转数字(具体见上)
    dec ax ; 因为地址表table里面的元素从0开始计数
    shl ax, 1; 左移一位,因为16位偏移地址占用2个字节单元
    mov bx, ax
    jmp table[bx]; //寄存器相对寻址,(ip)=table+bx


disp1:
    mov dx, offset str1
    jmp output
disp2:
    mov dx, offset str2
    jmp output
disp3:
    mov dx, offset str3
    jmp output 
output:
    mov ah, 09h
    int 21h
    mov ah, 4ch
    int 21h
codesg ends
    end start

逻辑尺

逻辑尺,其实就是将有限个算式中的加减运算符用 01 进行区分,由此得到有限长度的 01 串。

一、题目

给定数组 \(x\ (x_1,x_2,...,x_{10})\)\(y\ (y_1,y_2,...,y_{10})\) ,要求计算下面的 \(z\ (z_1,z_2,...,z_{10})\),具体算式如下:

\[\begin{cases} z_1 &= x_1 + y_1 \\ z_2 &= x_2 + y_2 \\ z_3 &= x_3 - y_3 \\ z_4 &= x_4 - y_4 \\ z_5 &= x_5 - y_5 \\ z_6 &= x_6 + y_6 \\ z_7 &= x_7 - y_7 \\ z_8 &= x_8 - y_8 \\ z_9 &= x_9 + y_9 \\ z_{10}&= x_{10} + y_{10} \\ \end{cases} \]

二、分析

注意只有\(10\)项,每一项的\(z_i(1\leq i\leq 10)\)都是由 \(x_i\)\(y_i\) 进行加或减的运算,我们用 1 表示减法、0 表示加法,因此,从第一条式(在低位)到第10条式(在高位)的加减符号便能够得到一条 01 串:0000,0000,1101,1100b(16进制为 00dch),接着我们只需要循环依次遍历 \(z_i\) 便能够得出结果了。

三、代码

datasg segment
    x dw x1,x2,x3,x4,x5,x6,x7,x8,x9,x10 ;注意,存放的应该是x数组每个元素的值,这里写成x1,x2,...只是占个位
    y dw y1,y2,y3,y4,y5,y6,y7,y8,y9,y10 ;同上理y1,y2,...只是占个位,实际情况是要填上数字的
    z dw z1,z2,z3,z4,z5,z6,z7,z8,z9,z10 ;同上理z1,z2,...只是占个位,实际情况是要填上数字的
    logic_rule dw 00dch ; 0000,0000,1101,1100,其中1表示减法,0表示加法
datasg ends

stacks segment
    ;
stacks ends

codesg segment
    assume cs:codesg, ds:datasg, ss:stacks
start:
    mov ax, datasg
    mov ds, ax

    mov bx, 0
    mov cx, 10 ;10个元素
    mov dx, logic_rule
next:
    mov ax, x[bx] 
    shr dx, 1    ;右移,使得CF标记逻辑尺的最低位
    jc subtract  ;当前最低位为1,进行减法运算
    add ax, y[bx];当前最低位为0,进行加法运算
    jmp restore 
subtract:
    sub ax, y[bx]
restore:
    mov z[bx], ax;将结果存到z数组中
    add bx, 2 ;找到数组下一个字单元
    loop next

    mov ah, 4ch
    int 21h
codesg ends
    end start

其他算法

  • 阶乘运算(递归实现)
    《汇编语言程序设计教程》(卜艳萍,清华出版社)P175

  • 辗转相除法
    《汇编语言程序设计教程》(卜艳萍,清华出版社)P165

  • 冒泡排序
    《汇编语言程序设计教程》(卜艳萍,清华出版社)P157

posted @ 2020-11-26 00:44  J_StrawHat  阅读(311)  评论(0编辑  收藏  举报