Menu

2025-05-21-汇编语言键盘输入输出

post on 21 May 2025 about 6722words require 23min
CC BY 4.0 (除特别声明或转载文章外)
如果这篇博客帮助到你,可以请我喝一杯咖啡~

2025-05-21-汇编语言键盘输入输出

参考博客

任务 1

把数据段中 1 维数组 AA_1 变量地址中连续 7 个数(1,3,5,7,2,4,6)读出,把每个数加 2 后再存入到数据段中 BB_1 数组开始的标号地址中去,并显示出 BB_1 数组中每个数来(之间用空格分开)

DATA  SEGMENT
    ORG 0100H
  AA_1    DB     1,3,5,7,2,4,6
    ORG 0150H
  BB_1    DB     7 dup(?) 
  COUNT  DW     7 _;给7设置别名_
DATA  ENDS
CSEG  SEGMENT
      ASSUME CS: CSEG,DS:DATA
START:MOV    AX, DATA
      MOV    DS, AX
      MOV    CX, COUNT
      LEA    SI, AA_1    _;取偏移地址(或者使用offset) _
      LEA    DI, BB_1   
LP1:  MOV    AL, [SI]   _;寄存器间接寻址方式可以改成相对寻址方式 _
      ADD    AL,2   
      MOV    [DI], AL   
      INC SI    _;SI+1        _
      INC DI    _;DI+1           _
      LOOP  LP1 _;不是0就转到标号LP1_
      LEA   SI, BB_1  
      MOV CX, COUNT
DISP:  MOV  DL, [SI]
ADD  DL, 30H    
MOV    AH,02    _;显示输出(要背下来)_
INT    21H   
      MOV DL,' '   _;每显示输出一个数后,输出一个空格  _
      MOV AH,2   
      INT 21H  
      INC   SI
      LOOP  DISP   
MOV    AH,4CH   
      INT    21H
CSEG  ENDS
      END    START

任务 2

从键盘接收一个小写字母,然后找出它的前导字符和后续字符,再按顺序显示这三个字符。

CSEG SEGMENT
    ASSUME CS:CSEG
    ORG 100H
START:
      ; 从键盘接收一个小写字母
      MOV AH,1
      INT 21H

      MOV DL,AL
      DEC DL
  
      ; 设置循环次数
      MOV CX,3       ; 设置循环次数
  
LOOP1:
      ; 检查输入是否为小写字母
      CMP DL,'a'
      JB EXIT        ; 小于'a'就退出
      CMP DL,'z'
      JA EXIT        ; 大于'z'就退出
  
      MOV AH,2
      INT 21H
  
      ADD DL,1

      LOOP LOOP1     ; CX减1,若不为0则跳转到LOOP1继续循环
  
EXIT:
      MOV AH,4CH
      INT 21H
  
CSEG ENDS
      END START

(1) 指令 MOV AH, 02H 中 02H 的含义是?

在 x86 汇编中,MOV AH, 02H 指令将立即数 02H 存入 AH 寄存器。在 DOS 中断服务程序中,这个值有特殊含义:将 AH 设置为 02H 是为了调用 DOS 的 2 号功能 - 显示字符输出功能

当执行 INT 21H 中断调用时,系统会根据 AH 中的值来确定要执行的 DOS 功能。02H 功能会将 DL 寄存器中的 ASCII 字符显示到标准输出设备(通常是屏幕)上。

(2) 指令 ADD DL, 30H 的作用是什么?

指令 ADD DL, 30H 的作用是将 DL 寄存器中的数值转换为对应的 ASCII 码字符

具体来说:

  • 30H 是 ASCII 码中数字’0’的十六进制表示
  • 当 DL 中存储的是一个 0-9 的数值时,加上 30H 后会变成对应数字的 ASCII 码
  • 例如:DL=3,执行 ADD DL,30H 后,DL=33H,这是数字’3’的 ASCII 码

在这个程序中,由于 BB_1 数组中存储的是加 2 后的数值(3,5,7,9,4,6,8),需要将这些数值转换为 ASCII 码才能正确显示,否则会显示为不可见的控制字符。

注意:这种转换方法只适用于单个十进制数字(0-9)。对于大于 9 的数字,这种简单的加 30H 方法会产生错误的字符。

(3) 除了参考程序中用的访问方式,还可以用什么方式访问 AA_1 数组里的元素?

参考程序中使用的是基于寄存器间接寻址方式(使用 SI 作为指针)来访问 AA_1 数组元素。除此之外,还可以使用以下方式:

  1. 直接寻址
MOV AL, AA_1[0]    ; 访问第一个元素
MOV AL, AA_1[1]    ; 访问第二个元素
  1. 基址寻址
MOV BX, OFFSET AA_1
MOV AL, [BX]       ; 访问第一个元素
MOV AL, [BX+1]     ; 访问第二个元素
  1. 变址寻址
MOV SI, 0
MOV AL, AA_1[SI]   ; 访问第一个元素
INC SI
MOV AL, AA_1[SI]   ; 访问第二个元素
  1. 基址加变址寻址
MOV BX, OFFSET AA_1
MOV SI, 0
MOV AL, [BX+SI]    ; 访问第一个元素
INC SI
MOV AL, [BX+SI]    ; 访问第二个元素
  1. 基址加变址加位移寻址
MOV BX, OFFSET AA_1
MOV SI, 1
MOV AL, [BX+SI-1]  ; 访问第一个元素
MOV AL, [BX+SI]    ; 访问第二个元素

任务 3

已知 DATAX 和 DATAY 单元各存放一个带符号字节数据,从键盘上接收加(+)、减(-)、乘(*)或除(/)符号,然后完成相应运算,把结果显示在屏幕上。

求绝对值(基础模块)

DATA SEGMENT
    num DB -6         _; 8位有符号数_
    buf DB 4 DUP(?)   _; 最多3位+1_
DATA ENDS

CODE SEGMENT
    ASSUME CS:CODE, DS:DATA
START:
    MOV AX, DATA
    MOV DS, AX

    MOV AL, num       _; 取数_
    CBW               _; 符号扩展到AX_

    CMP AL, 0
    JGE PRINT_DEC     _; 如果是正数,直接打印_
    NEG AL            _; 取绝对值_
    CBW

PRINT_DEC:
    _; AX中为正数,转十进制_
    MOV SI, 0
    MOV BX, 10

CONV_LOOP:
    XOR DX, DX
    DIV BX            _; AX / 10, 商->AX, 余数->DX_
    ADD DL, '0'
    MOV buf[SI], DL
    INC SI
    CMP AX, 0
    JNZ CONV_LOOP

PRINT_LOOP:
    DEC SI
    MOV DL, buf[SI]
    MOV AH, 2
    INT 21H
    CMP SI, 0
    JNZ PRINT_LOOP

    MOV AH, 4CH
    INT 21H
CODE ENDS
    END START

完整代码

DATA SEGMENT
    DATAX DB 6      _; 带符号字节数据,负数_
    DATAY DB -2      _; 带符号字节数据,正数_
    RESULT DB 0
    BUF DB 4 DUP(?)
DATA ENDS

CSEG SEGMENT
    ASSUME CS:CSEG,DS:DATA
START:
      MOV AX,DATA
      MOV DS,AX

      _;从键盘接收+,-,*,/_
      MOV AH,1
      INT 21H

      CMP AL,'+'
      JE ADD_OP
      CMP AL,'-'
      JE SUB_OP
      CMP AL,'*'
      JE MUL_OP
      CMP AL,'/'
      JE DIV_OP

ADD_OP:
      MOV AL,DATAX
      MOV BL,DATAY
      ADD AL,BL
      MOV RESULT,AL
      JMP SHOW_RESULT  
  
SUB_OP:
      MOV AL,DATAX
      MOV BL,DATAY
      SUB AL,BL
      MOV RESULT,AL
      JMP SHOW_RESULT  

MUL_OP:
_; 字节乘法: _
_; (AL)*(OPS8)→AX_
      MOV AL,DATAX
      MOV BL,DATAY
      IMUL BL
      MOV RESULT,AL
      JMP SHOW_RESULT  

DIV_OP:
      MOV AL,DATAX
      MOV BL,DATAY
      CBW _;将AL符号扩展到AX_
      IDIV BL
      MOV RESULT,AL
      JMP SHOW_RESULT  

_;难点在打印,将每次除以10,把余数入栈,然后出栈,打印_
SHOW_RESULT:
    MOV AL, RESULT
    CBW
    CMP AL, 0
    JGE SHOW
    PUSH AX           _; 保存原始AL_
    MOV DL, '-'
    MOV AH, 2
    INT 21H
    POP AX            _; 恢复AL_
    NEG AL
    CBW

SHOW:
    MOV SI,0            _; SI为BUF索引_
    MOV BX,10

CONV_LOOP:
    MOV DX,0
    DIV BX
    ADD DL,'0'
    MOV BUF[SI],DL
    INC SI
    CMP AX,0
    JNZ CONV_LOOP

PRINT_LOOP:
    DEC SI
    MOV DL,BUF[SI]
    MOV AH,2
    INT 21H
    CMP SI,0
    JNZ PRINT_LOOP
    JMP EXIT

EXIT:
      MOV AH,4CH
      INT 21H
  
CSEG ENDS
      END START

程序逻辑流程图

17478844701641747884469368.png

注意事项

  • 当 RESULT 为负数时,我在打印结果的时候我们需要先打印’-’号,INT 21H 会把 AL 的值重新设置
  • 使用 IDIV BX 命令前需要我们将 AX 使用 CBW 命令将 AL 扩展为 AX

进一步的修改

_; 简单计算器程序 - 对DATAX和DATAY中的有符号字节数据进行四则运算_
_; 支持加(+)、减(-)、乘(*)、除(/)四种运算符_

.MODEL SMALL
.STACK 100H

.DATA
DATAX   DB  ?           _; 第一个操作数_
DATAY   DB  ?           _; 第二个操作数_
MSG1    DB  'Input first number: $'
MSG2    DB  0DH, 0AH, 'Input second number: $'
MSG3    DB  0DH, 0AH, 'Input operator (+, -, *, /): $'
MSG4    DB  0DH, 0AH, 'Result: $'
MSG5    DB  0DH, 0AH, 'Division by zero! $'
TEMP    DW  ?           _; 临时存储乘法或除法结果_
NEG_FLAG DB  0           _; 负数标志 (1表示结果为负)_
BUF     DB  6 DUP(?)    _; 用于存储结果字符串_
NEG_INPUT DB 0          _; 输入负号标志_
VALUE     DB 0          _; 输入值_

.CODE
MAIN PROC
    MOV AX, @DATA       _; 初始化数据段_
    MOV DS, AX

    _; 显示第一条提示消息_
    LEA DX, MSG1
    MOV AH, 09H
    INT 21H

    _; 输入第一个数字_
    CALL INPUT_SIGNED_BYTE
    MOV DATAX, AL

    _; 显示第二条提示消息_
    LEA DX, MSG2
    MOV AH, 09H
    INT 21H

    _; 输入第二个数字_
    CALL INPUT_SIGNED_BYTE
    MOV DATAY, AL

    _; 显示操作符提示消息_
    LEA DX, MSG3
    MOV AH, 09H
    INT 21H

    _; 输入操作符_
    MOV AH, 01H
    INT 21H
    MOV BL, AL          _; 保存操作符在BL中_

    _; 显示结果提示消息_
    LEA DX, MSG4
    MOV AH, 09H
    INT 21H

    _; 根据操作符执行相应运算_
    CMP BL, '+'         _; 检查是否为加法_
    JE  DO_ADD
    CMP BL, '-'         _; 检查是否为减法_
    JE  DO_SUB
    CMP BL, '*'         _; 检查是否为乘法_
    JE  DO_MUL
    CMP BL, '/'         _; 检查是否为除法_
    JE  DO_DIV
    JMP EXIT            _; 如果不是有效操作符,直接退出_

DO_ADD:
    MOV AL, DATAX       _; 加法运算_
    ADD AL, DATAY
    JMP DISPLAY_RESULT

DO_SUB:
    MOV AL, DATAX       _; 减法运算_
    SUB AL, DATAY
    JMP DISPLAY_RESULT

DO_MUL:
    MOV AL, DATAX       _; 乘法运算_
    MOV BL, DATAY
    CALL SIGNED_MUL
    JMP DISPLAY_AX

DO_DIV:
    MOV AL, DATAX       _; 除法运算_
    MOV BL, DATAY
    CALL SIGNED_DIV
    JMP DISPLAY_AX

DISPLAY_RESULT:
    _; 结果在AL中,转换为字符串并显示_
    MOV AH, 0           _; 清零AH,结果扩展到AX_
    CMP AL, 0
    JGE POSITIVE
    NEG AL              _; 如果为负,取绝对值_
    MOV NEG_FLAG, 1     _; 设置负数标志_
    JMP CONTINUE
POSITIVE:
    MOV NEG_FLAG, 0     _; 清除负数标志_
CONTINUE:
    MOV AX, AX          _; AX中现在是结果的绝对值_

DISPLAY_AX:
    _; 显示AX中的有符号结果_
    CALL DISPLAY_SIGNED_NUM
  
EXIT:
    _; 程序结束_
    MOV AH, 4CH
    INT 21H
MAIN ENDP

_; 输入有符号字节的过程_
INPUT_SIGNED_BYTE PROC
    _; 检查第一个字符是否为负号_
    MOV AH, 01H
    INT 21H
    CMP AL, '-'
    JNE CHECK_DIGIT1
    MOV NEG_INPUT, 1    _; 设置负号标志_
    MOV AH, 01H         _; 再次读取一个字符_
    INT 21H
  
CHECK_DIGIT1:
    _; 检查输入字符是否为数字_
    CMP AL, '0'
    JL EXIT_INPUT       _; 如果小于'0',不是数字_
    CMP AL, '9'
    JG EXIT_INPUT       _; 如果大于'9',不是数字_
  
    _; 转换为数值并保存_
    SUB AL, '0'
    MOV VALUE, AL
  
    _; 读取可能存在的第二个数字_
    MOV AH, 01H
    INT 21H
  
    _; 检查第二个字符是否为数字_
    CMP AL, '0'
    JL EXIT_INPUT2      _; 如果小于'0',不是数字_
    CMP AL, '9'
    JG EXIT_INPUT2      _; 如果大于'9',不是数字_
  
    _; 处理第二个数字_
    SUB AL, '0'
    MOV BL, VALUE       _; 将第一个数字移到BL_
    MOV BH, 0           _; 清零BH_
    MOV CX, 10
    MUL CX              _; 将BX乘以10_
    ADD BL, AL          _; 加上第二个数字_
    MOV VALUE, BL
    JMP EXIT_INPUT
  
EXIT_INPUT2:
    _; 如果第二个字符不是数字,将它放回缓冲区(模拟未读取)_
    MOV DL, AL
    MOV AH, 02H
    INT 21H
  
EXIT_INPUT:
    _; 返回结果_
    MOV AL, VALUE
    CMP NEG_INPUT, 1
    JNE RETURN_INPUT
    NEG AL              _; 如果有负号标志,取负值_
  
RETURN_INPUT:
    RET
INPUT_SIGNED_BYTE ENDP

_; 有符号乘法过程 - 结果在AX中_
SIGNED_MUL PROC
    _; 保存符号_
    MOV CL, AL          _; 保存第一个操作数_
    MOV CH, BL          _; 保存第二个操作数_
  
    _; 取绝对值_
    CMP AL, 0
    JGE ABS1_DONE
    NEG AL
ABS1_DONE:
    CMP BL, 0
    JGE ABS2_DONE
    NEG BL
ABS2_DONE:
  
    _; 执行无符号乘法_
    MUL BL              _; AX = AL * BL_
  
    _; 确定结果符号_
    MOV BH, 0           _; 清零BH_
    MOV BL, 0           _; 假设结果为正_
  
    CMP CL, 0           _; 检查第一个操作数符号_
    JGE CHECK_OP2_MUL
    XOR BL, 1           _; 翻转符号位_
  
CHECK_OP2_MUL:
    CMP CH, 0           _; 检查第二个操作数符号_
    JGE APPLY_SIGN_MUL
    XOR BL, 1           _; 翻转符号位_
  
APPLY_SIGN_MUL:
    CMP BL, 1           _; 检查是否需要取负_
    JNE RETURN_MUL
    NEG AX              _; 对结果取负_
  
RETURN_MUL:
    RET
SIGNED_MUL ENDP

_; 有符号除法过程 - 结果在AX中_
SIGNED_DIV PROC
    _; 检查除数是否为零_
    CMP BL, 0
    JNE NOT_ZERO_DIV
  
    _; 除数为零处理_
    LEA DX, MSG5        _; 显示除零错误消息_
    MOV AH, 09H
    INT 21H
    MOV AX, 0           _; 返回0_
    RET
  
NOT_ZERO_DIV:
    _; 保存符号_
    MOV CL, AL          _; 保存第一个操作数_
    MOV CH, BL          _; 保存第二个操作数_
  
    _; 取绝对值_
    CMP AL, 0
    JGE ABS1_DIV_DONE
    NEG AL
ABS1_DIV_DONE:
    CMP BL, 0
    JGE ABS2_DIV_DONE
    NEG BL
ABS2_DIV_DONE:
  
    _; 执行无符号除法_
    MOV AH, 0           _; 扩展AL到AX_
    DIV BL              _; AL = AX / BL, AH = AX % BL_
    MOV AH, 0           _; 清零AH(我们只关心商)_
  
    _; 确定结果符号_
    MOV BH, 0           _; 清零BH_
    MOV BL, 0           _; 假设结果为正_
  
    CMP CL, 0           _; 检查第一个操作数符号_
    JGE CHECK_OP2_DIV
    XOR BL, 1           _; 翻转符号位_
  
CHECK_OP2_DIV:
    CMP CH, 0           _; 检查第二个操作数符号_
    JGE APPLY_SIGN_DIV
    XOR BL, 1           _; 翻转符号位_
  
APPLY_SIGN_DIV:
    CMP BL, 1           _; 检查是否需要取负_
    JNE RETURN_DIV
    NEG AX              _; 对结果取负_
  
RETURN_DIV:
    RET
SIGNED_DIV ENDP

_; 显示有符号数字_
DISPLAY_SIGNED_NUM PROC
    _; 检查符号_
    CMP AX, 0
    JGE POS_NUM
  
    _; 显示负号_
    MOV DL, '-'
    MOV AH, 02H
    INT 21H
    NEG AX              _; 取绝对值_
  
POS_NUM:
    _; 将数字转换为字符串并显示_
    MOV CX, 0           _; 初始化计数器_
    MOV BX, 10          _; 基数(十进制)_
  
    _; 将数字转换为字符串(逆序)_
CONVERT_LOOP:
    MOV DX, 0           _; 清零DX_
    DIV BX              _; AX / 10, 商在AX,余数在DX_
    PUSH DX             _; 保存余数_
    INC CX              _; 增加计数器_
    CMP AX, 0           _; 检查商是否为0_
    JNE CONVERT_LOOP    _; 如果不是0,继续转换_
  
    _; 显示字符串(正序)_
DISPLAY_LOOP:
    POP DX              _; 取出一个数字_
    ADD DL, '0'         _; 转换为ASCII_
    MOV AH, 02H         _; 显示字符功能_
    INT 21H
    LOOP DISPLAY_LOOP   _; 循环直到所有数字显示完毕_
  
    RET
DISPLAY_SIGNED_NUM ENDP

END MAIN
Loading comments...