一些汇编练习

一些汇编练习,代码可能有有些bug,不是太完美,更多请见
https://lartpang.github.io/myasm/
仓库调整了下,放到了组织下,https://github.com/DUT-Lab/AsmExamples

习题4:

  1. 从屏幕上输入大写字母,转换为小写字母并输出(生成.com文件)
    要求:程序具有可读性、容错性

思路:

由于要生成.com文件,所以需要将文件代码设置到一个代码段里。本问题涉及到读入与显示,这里可以使用21h中断的1,2,9号子功能。
读入字符后,将al中存放的字符通过判断其是否为大写字母来保证程序的正确性。利用大小写ascii码上的对应关系,实现了大小写的转化。

代码如下:

code    segment para
    assume  cs:code, ds:code, ss:code
    org     0100h
    main    proc near
    again:
        lea     dx, str_dis
        mov     ah, 9
        int     21h             ;显示初始字符串,提示输入
        mov     ah, 1
        int     21h             ;获得输入字符,存到al里
        cmp     al, 'A'         ;判断输入字符是否是A~Z的字符
        jb      error
        cmp     al, 'Z'
        ja      error           ;保证输入合法
        add     al, 20h         ;大写+20h=小写
        mov     str_num, ax     ;将ax保存
        lea     dx, str_res
        mov     ah, 9
        int     21h             ;显示结果提示字符串
        mov     ax, str_num     ;将ax恢复
        mov     dl, al
        mov     ah, 2
        int     21h             ;显示最终结果
        mov     ax, 4c00h
        int     21h
    error:
        lea     dx, str_err
        mov     ah, 9
        int     21h
        jmp     again
    main    endp
    str_num dw 0000h
    str_dis db 0dh, 0ah, 'Please input(A~Z):$'
    str_res db 0dh, 0ah, 'The result is:$'
    str_err db 0dh, 0ah, 'The data of input is invalid!$'
code    ends
    end     main

  1. 编写一子程序 asc2bin ,将 ASCII 转换为二进制数
    要求:
    输入参数:AL 中存放需要转换的 ASCII
    输出参数:AL 中存放转换后的二进制数并返回

思路:

对于ascii码,先都当做数字来处理,即都减30h,在与9比大小,大于9的说明是10~15,再减7即可。

代码如下:

asc2bin     proc
    sub     al, 30h         ;先都当做表示数字的acsii
    cmp     al, 9           ;与9比较,小于等于9的,直接跳转,大
                            ;于9的,仍然需要处理
    jbe     asc2bin_sub     ;跳转子程序
    sub     al, 7           ;继续减7
asc2bin_sub:
    ret                     ;返回主程序
asc2bin     endp
  1. 内存中存放8个16位有符号数,求8个数值之和,并将结果存放在内存变量SUM中
    注:程序中应用到字扩展为双字的指令CWD

思路:

通过使用循环加法,扩展双字的指令,转化为双字的计算加和。

代码如下:

data    segment para
    buf dw -1, 2, -33, 44, -555, 666, -7777, 8888
    sum dd 0
data    ends
ss_seg  segment stack
    dw  100 dup(0)
ss_seg  ends
code    segment para
    assume  cs:code, ds:data, ss:ss_seg
        main    proc far
        mov     ax, data
        mov     ds, ax              ;指定数据位置
        lea     bx, buf             ;bx指向buf首地址
        mov     cx, 8               ;指定循环次数
    w2d:
        mov     ax, [bx]
        cwd                         ;有符号数字扩展为双字,默认使用{dx,ax}
        add     word ptr sum, ax    ;32 位数相加
        adc     word ptr sum + 2, dx
        inc     bx
        inc     bx
        loop    w2d                 ;循环 8 次
        mov     ax, 4c00h
        int     21h
    main    endp
code    ends
    end     main
  1. 内存中存放 8 个 8 位有符号数,请按从大到小顺序排列

思路:

利用冒泡排序,设置两个循环,将相邻的数字进行比较,大的放在低位地址,小的放在高位地址。
对于循环的次数的设置这里有些需要注意的地方。一般而言,n个数的问题,外层循环需要n-1次,而内层循环也是需要n-1次比较,所以可以设置使用相同的循环计数器。只需要内层循环计数的时候将之前的压栈保存即可。

代码如下:

;低地址到高地址,大数到小数。
data    segment para
    buf db -1, 2, -33, 44, -55, 66, -77, 88
data    ends
stack   segment stack
    dw 100 dup(0)
stack   ends
code    segment para
    assume  cs:code, ds:data, ss:stack
    main    proc far
        ;将ds:bx指向存放数据的位置
        mov     ax, data
        mov     ds, ax
        mov     cx, 7           ;主循环仅需要7次
    loop_main:
        lea     bx, buf
        push    cx              ;cx=n, 子循环正好也要循环n次
                                ;压栈保存外循环循环次数
        mov     si, 0           ;标志是否有交换,当后面检测si==0时,说明
                                ;内部已经排序完毕
    loop_sub:
        ;取得第一个数据到ax里,将之与第二个数据比较大小
        ;ax大等于[bx]的话,保留原样
        ;ax小的话,就得将两个数据进行交换位置
        mov     al, [bx]
        cmp     al, [bx+1]
        jge     not_xchg        ;>=不交换
        ;ax小,进行交换
        xchg    al, [bx+1]
        mov     [bx], al
        mov     si, 1           ;标志有交换
    not_xchg:
        ;第一步交换完成,需要依次进行一遍
        inc     bx
        loop    loop_sub
        ;完毕后,只完成了初步的重排,这时数据段中的情况是,高地址存放最小的数
        ;还需要继续执行,之后每次都减少一次
        cmp     si, 0
        je      done_xchg
        ; 仍有交换,还需重复      
        pop     cx              ;出栈,恢复cx
        loop    loop_main
    done_xchg:
        mov     ax, 4c00h
        int     21h
    main    endp
code     ends
  end main
  1. 内存中有8个16位数,请编写程序将8个数倒序排放
    例:定义内存中8个数 buf dw 100, 3, 1, 20, 40, -2, 7, 10
    程序运行后,buf 开始应为: buf dw 10, 7, -2, 40, 20, 1, 3, 100

思路:

要实现倒序存放,利用栈的入栈出栈,或者反复循环交换。但是使用栈,更为便捷。

代码如下:

data    segment para
    buf dw -1, 2, -33, 44, -555, 666, -7777, 8888
data    ends
stack   segment stack
    dw 100 dup(0)
stack   ends
code    segment para
    assume  cs:code, ds:data, ss:stack
    main    proc far
        mov     ax, data
        mov     ds, ax
        lea     bx, buf
        mov     cx, 8
    stack_push:
        push    [bx]
        add     bx, 2
        loop    stack_push
        lea     bx, buf
        mov     cx, 8
    stack_pop:
        pop     [bx]
        add     bx, 2
        loop    stack_pop
        mov     ax, 4c00h
        int     21h
    main    endp
code    ends
    end     main
  1. 从键盘输入 4 位十进制数,然后以 16 进制形式显示在屏幕上
    例:键盘输入:1024 屏幕上应显示:0400H
    要求:键盘输入和显示结果时均应有提示

思路:

输入四位十进制数,将ascii码利用中断功能,读入(此处会对输入字符做出合法性判断),转换为十进制数并以二进制的形式存在内存里,最后,转换为十六进制,并拆分各个数字,显示ascii码输出。具体流程详见注释。

代码如下:

;具体流程:ASCII码输入->十进制->十六进制->ASCII码显示
;故需要子函数完成进制转换、ascii转换
data    segment para
    buf         db 4 dup(0)
    var_10      dw 0
    str_input   db 0dh, 0ah, 'Please input four numbers(0-9):$'
    str_error   db 0dh, 0ah, 'The input is error, please try again.$'
    str_output  db 0dh, 0ah, 'The hex result is:$'
data    ends
stack   segment stack
    dw 100 dup(0)
stack   ends
code    segment para
    assume  cs:code, ds:data, ss:stack   
    main    proc far
        mov     ax, data
        mov     ds, ax
    loop_again:                 ;输入有错误需要重新读取输入
        lea     dx, str_input   ;显示数据输入提示信息
        mov     ah, 9
        int     21h
        lea     bx, buf         ;用bx来作为buf的指代
        mov     cx, 4           ;循环输入4个数
    loop_input: 
        mov     ah, 1           ;输入数据
        int     21h
        cmp     al, '0'         ;判断输入字符是否为‘0’~‘9’
        jb      error
        cmp     al, '9'
        ja      error           ;非法字符,进行错误处理
        ;直接输入一个字符,处理一个字符
        sub     al, 30h         ;ASCII转换为二进制,得到 0-9
        mov     [bx], al        ;存入缓冲区
        inc     bx
        loop    loop_input
        jmp     input_valid     ;数据输入正确后 ,跳转到后续处理
    error: 
        lea     dx, str_error   ;显示错误提示信息
        mov     ah, 9
        int     21h
        jmp     loop_again      ;跳转到重新输入
    input_valid:                ;现在数据以二进制的形式存储
        mov     ax, 0           ;以(((0*10+dl3)*10+dl2)*10+dl1)*10+dl0计算
                                ;十进制数据结果存放到ax中
        mov     dx, 0           ;因为ax可以满足存放四位十进制数的需求
        mov     si, 10          
        mov     bx, 0
        mov     cx, 4
    loop_mul10: 
        mul     si              ;相乘后dx仍然保持 0
        mov     dl, [bx]        ;将buf数据取到dl中
        mov     dh, 0
        add     ax, dx
        inc     bx
        loop    loop_mul10      ;循环4次乘10
        mov     var_10, ax      ;得到的 4 位十进制数存放到 var_10 中
                                ;var_10共16位,存放十进制数对应的二进制
        lea     dx, str_output
        mov     ah, 9
        int     21h             ;显示输出提示符
        mov     ch, 4           ;以16进制显示输入的数据,循环四次
        mov     cl, 4
    loop_display: 
        rol     var_10, cl      ;循环左移4位,因为要先显示高位
        mov     ax, var_10
        and     ax, 000fh       ;仅保留最低四位
        call    bin2asc         ;二进制转换为 ASCII
        call    pchar           ;显示一个十六进制字符
        dec     ch
        jnz     loop_display
        mov     al, 'H'         ;显示十六进制‘H’记号
        call    pchar
        mov     ax, 4c00h
        int     21h
    main    endp

    ;功能: 将一个二进制数字转换为ASCII
    ;输入参数 : AL中存放二进制数(有效位只有低四位)
    ;输出参数 : AL中存放ASCII
    bin2asc     proc
        and     al, 0fh
        add     al, 30h
        cmp     al, 39h
        jbe     done_b2a
        add     al, 07h         ;是A~Z
    done_b2a: 
        ret                     ;返回
    bin2asc     endp

    ;功能:显示单个字符
    ;输入参数:AL中存放ASCII
    ;输出参数:无
    pchar   proc
        mov     dl, al
        mov     ah, 2
        int     21h
        ret                     ;返回
    pchar   endp
code    ends
    end     main
  1. 数据段从100H开始存放字符串str1,从200H开始存放str2,二者均以NULL字符为结束符,编写程序将str2拷贝到str1末尾,形成一个完整字符串
    例:
    ORG 100H
    str1 db 0dh, 0ah, ‘Hello ’, 0
    ORG 200H
    str2 db ‘Automation!’, 0
    程序运行后结果应为:
    str1 db 0dh, 0ah, ‘Hello Automation!’, 0

思路:

利用repnz scasb指令,将扫描的字符与al中预存的0比较,即查找str_first的结束位置,找到后,利用mov cx, count & rep movsb两条命令,以及count equ ($-str_second),将str_secon复制到str_first之后。
最后再利用lodsb,配合中断功能,显示最终的合并字符串。

代码如下:

data    segment para
    ORG     100H
    str_first   db 0dh, 0ah, 'Hello ', 0
    ORG     200H
    str_second  db 'Automation! ', 0
    count       equ ($-str_second)
data    ends
ss_seg  segment stack
    dw 100 dup(0)
ss_seg  ends
code    segment para
    assume  cs:code, ds:data, ss:ss_seg
    main    proc far
        mov     ax, data
        mov     ds, ax
        mov     es, ax
        lea     di, str_first   ;es:di 指向str_first首地址
        mov     al, 0
        repnz   scasb           ;查找到str_first结束符NULL,最后di会多加一次
        dec     di              ;让es:di指向该位置
        lea     si, str_second  ;ds:si指向str_second首地址
        cld                     ;DF置零,右移
        mov     cx, count
        rep     movsb           ;重复搬移
        lea     si, str_first   ;ds:si 指向拷贝后的 str_first 首地址
    display: 
        lodsb                   ;显示拷贝后的 str_first 字符串
        cmp     al, 0
        jz      exit            ;显示完毕
        call    pchar
        jmp     display
    exit: 
        mov     ax, 4c00h
        int     21h
    main    endp

    ;功能:显示单个字符
    ;输入参数:AL中存放ASCII
    ;输出参数:无
    pchar   proc
        mov     dl, al
        mov     ah, 2
        int     21h
        ret
    pchar   endp
code    ends
    end     main
  1. 以 10 进制形式显示 内存中一有符号字节数据
    例:var db 0ffH
    屏幕应显示:The result is: -1
    代码如下:

思路:

主要是二进制转化为十进制后拆分成单个数字,在将之处理为ascii码。
首先需要与0比较,利用jg/jl等有符号数比较指令实现跳转处理。分别将第一个字符设置为对应的符号。
再将实际数值部分进行处理,转换为ascii码,进行显示。

代码如下:

;思路:二进制->十进制->ascii码
data    segment para
    var_signed      db 0ffH
    str_result      db 0dh, 0ah, 'The result is: '
    num_signed      db 4 dup(' ');因为有符号字节数,最多就是三位十进制
                    db '$'
data    ends
stack   segment stack
    dw 100 dup(0)
stack   ends
code    segment para
    assume  cs:code, ds:data, ss:stack
    main    proc far
        mov     ax, data
        mov     ds, ax
        mov     num_signed, '+'
        cmp     var_signed, 0   ;判断 var_signed 是正数 ,还是负数
        jge     is_pos          ;>= 为正数
        mov     num_signed, '-' ;<  为负数
        neg     var_signed      ;若 var_signed 为负,则补码的相反数
    is_pos: 
        mov     al, var_signed
        mov     cx, 3
        mov     dl, 10          ;进制
        lea     bx, num_signed+3;倒着放数据
    again: 
        mov     ah, 0           ;高八位置零
        div     dl
        add     ah, 30h         ;余数 ah
        mov     [bx], ah
        dec     bx
        loop    again           ;循环 3 次,分别得到百、十、个位
    done:
        lea     dx, str_result  ;显示 10 进制数
        mov     ah, 9
        int     21h
    exit: 
        mov     ax, 4c00h
        int     21h
    main    endp
code    ends
    end     main
  1. 将一个16位的无符号数var,转换为非压缩格式BCD码,存放在内存中buf开始的单元中。(按高位在 前、低位在后的顺序存放)

思路:

利用二进制转换十进制:((0*2 + B15)*2 + B14)*2 + ? + )*2 + B0
在运算过程中利用非压缩格式bcd码调整指令aaa,处理为非压缩格式bcd码,使得最终结果即为非压缩格式bcd码。

代码如下:

;二进制数 0FFFH,十进制 4095,非压缩BCD 4 0 9 5 各一个字节的低四位
;二进制转换十进制:((0*2 + B15)*2 + B14)*2 + ? + )*2 + B0
data    segment para
    buf     db 4 dup(0)
    var     dw 0FFFH    
data    ends
stack   segment stack
    dw  100 dup(0)
stack   ends
code    segment para 
    assume  cs:code, ds:data, ss:stack
    main    proc far
        mov     ax, data
        mov     ds, ax
        mov     cx, 16          ;要进行十六次移位,利用adc获得
    loop_wai: 
        shl     var, 1          ;得到 var 的 Bi 位
        mov     bx, 3
        push    cx
        mov     cx, 4
    loop_nei:                   
        mov     al, [bx]        ;执行 buf*2 + Bi 操作
        adc     al, al
        aaa                     ;非压缩格式 BCD 码调整
        mov     [bx], al
        dec     bx
        loop    loop_nei        ;内循环为 4 次
        pop     cx
        loop    loop_wai        ;外循环为 16 次
    exit: 
        mov     ax, 4c00h
        int     21h
    main    endp
code    ends
    end     main
  1. 内存中有两个32位有符号数,请编写完整汇编语言程序,将二者相乘,结果保存在64位有符号数RESULT中

思路:

两个32位与符号数相乘,只要处理好符号位,就可以转化为无符号数相乘。

代码如下:

data    segment para
    num_1   dd 80000002h
    num_2   dd 80005000h
    result  dw 4 dup (?)
data    ends
ss_seg  segment stack
    dw 100 dup (0)
ss_seg  ends
code    segment para
    assume  cs:code, ds:data, ss:ss_seg
    main    proc far
        mov     ax, data
        mov     ds, ax
        shl     byte ptr [num_1+3], 1	;处理符号位
        jc      num_1_l0 				;num_1<0
        xor     ax, ax   
        shr     byte ptr [num_1+3], 1	;剥离符号位
        mov     bh, 0h					;用bh存放符号位
        jmp     next					;符号位被置零
    num_1_l0:;num_1<0
        mov     bh, 1h					;用bh存放符号位
    next:
        shl     byte ptr [num_2+3], 1	;对num_2做相同处理
        jc      num_2_l0
        xor     ax, ax
        shr     byte ptr [num_2+3], 1
        mov     bl, 0h
        jmp     continue
    num_2_l0:;num_2<0
        mov     bl, 1h
    continue:							; 当做无符号数处理,计算相乘
        lea     si, num_1
        lea     di, num_2
        mov     ax, [si]
        mov     dx, [di]
        mul     dx
        mov     [result],ax
        mov     [result+2],dx
        mov     ax, [si+2]
        mov     dx, [di]
        mul     dx
        add     [result+2],ax
        adc     [result+4],dx
        mov     ax, [si]
        mov     dx, [di+2]
        mul     dx
        add     [result+2],ax
        adc     [result+4],dx
        adc     [result+6],0
        mov     ax, [si+2]
        mov     dx, [di+2]
        mul     dx
        add     [result+4],ax
        adc     [result+6],dx
        sub     bh, bl					;处理符号
        jnz     bh_nz_bl				;num_1 num_2异号
        and     [result+7], 01111111b	;同号就将结果最高位置0
        jmp     exit
    bh_nz_bl:;不同号
        or      [result+7], 10000000b	;异号就将结果最高位置1
    exit:
        mov     ah, 4ch
        int     21h
    main    endp
code    ends
    end     main
  1. 将一个 8 位压缩 BCD 码转换为二进制数

思路:

对于四位压缩bcd码的过程比较简单,可以利用不断循环左移四位,将高位字节的两个bcd码置于字单元的最后位置,整体赋给寄存器,现在寄存器里存放的是一位压缩bcd码,乘以10再加上下一位如此获得的bcd码,反复如此,就可以获得四位的压缩bcd对应的十进制,存放到内存中,就变为了二进制。
而对于八位压缩bcd码,则可以拆成高四位对应的十进制,乘以10000,加上低四位对应的十进制数。这里就是这样处理的。
高四位低四位分别求出了对应的十进制,按上述规则相加,得到了最终结果。

代码如下:

data    segment para
    BCD_num     dd 12345678h
    bin_num     db 4 dup(0)
    loop_num    db 2
data    ends
ss_seg  segment stack
    dw      100 dup(0)
ss_seg  ends
code    segment para 
    assume  cs:code, ds:data, ss:ss_seg
    main    proc far
        mov     ax, data
        mov     ds, ax
        mov     si, 10
        mov     ax, 0
        mov     dl, 2   ;分两段
        mov     di, 2
    again_loop:
        mov     cx, 0404h	;ch 乘法循环计数器; cl 移位计数器
    again_mul:
        mul     si
        rol     word ptr [BCD_num+di], cl
        mov     bx, word ptr [BCD_num+di]
        and     bx, 000fh
        add     ax, bx
        dec     ch
        jnz     again_mul
        mov     word ptr [bin_num+di], ax
        xor     di, di
        xor     ax, ax
        sub     [loop_num], 1
        jnz     again_loop
        ; 高四位对应的十进制乘以10,加上低四位对应的十进制
        mov     ax, word ptr [bin_num+2]
        mov     bx, 10000
        mul     bx
        add     ax, word ptr [bin_num]
        adc     dx, 0
        mov     word ptr [bin_num], ax
        mov     word ptr [bin_num+2], dx
        mov     ax, 4c00h
        int     21h
    main    endp
code    ends
    end     main

选作题:

  1. 内存中从str开始存放一字符串,结束符为NULL字符,请编写程序统计该字符串中单词的个数
    例:str1 db 0dh, 0ah, ‘Hello world, welcome to DUT. CPU is central
    processing unit!’, 0h
    统计 ’….’ 中的单词个数,结果为10

思路:

如题所述,主要统计空格。通过扫描字符串,统计空格,但是有效的空格前一个字符是有效的字符,只有这样的空格才会统计。

代码如下:

data    segment para
jj    string  db 0dh, 0ah, 'Hello world, welcome to DUT. CPU is central '
            db 'processing unit!', 0h
    words   dw 0
data    ends
stack   segment stack
    db  256 dup(0)
stack   ends
code    segment para
    assume  cs:code, ds:data, ss:stack
    main    proc far
        mov     ax, data
        mov     ds, ax
        mov     cx, 0       ;用 cx 存放单词数
        lea     si, string
        mov     bl, ' '     ;bl 总保存当前字符的前一个字符
        cld                 ;保证右移
    load_al: 
        lodsb               ;从ds:si中以字节为单位获取数据,al只判断0以及空格
        and     al, al      ;判断 al 是否为结束符 0,al=0 zf=1,al!=0,zf=0
        jz      al_0
        cmp     al, ' '     ;比较是否是空格,只对空格计数
        jnz     bl_new
    al_douhao:              ;处理重复计数:计数只是根据空格的个数来计算。
                            ;重复计数主要是因为多个空格以及逗号空格连续所致。
        cmp     bl, ' '     ;比较前一个字符是否为空格,如果是则此空格
                            ;不能算一个单词,即为了防止多个空格的情况
        jz      bl_new
        inc     cx          ;只有当前字符为 ' '或',' 而且前一个字符为有
                            ;效字符时,才对单词数加 1
        jmp     bl_new
    bl_new: 
        mov     bl, al      ;进入这里表明此时 al 中内容不是 0 或者 ' '
                            ;符号 ,保存 al 到 bl
        jmp     load_al
    al_0:                   ;判断结束符前面是否有' ',删掉多余的计数
        cmp     bl, ' '     
        jz      done
        inc     cx          ;若结束符前是一个有效字符,那么单词数应该加 1
    done: 
        mov     words, cx
        mov     ax, 4c00h
        int     21h
    main    endp
code    ends
    end     main
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值