这篇文章上次修改于 181 天前,可能其部分内容已经发生变化,如有疑问可询问作者。
2025-05-21-汇编语言键盘输入输出
参考博客
任务 1
把数据段中 1 维数组 AA_1 变量地址中连续 7 个数(1,3,5,7,2,4,6)读出,把每个数加 2 后再存入到数据段中 BB_1 数组开始的标号地址中去,并显示出 BB_1 数组中每个数来(之间用空格分开)
任务 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 数组元素。除此之外,还可以使用以下方式:
- 直接寻址:
MOV AL, AA_1[0] ; 访问第一个元素
MOV AL, AA_1[1] ; 访问第二个元素
- 基址寻址:
MOV BX, OFFSET AA_1
MOV AL, [BX] ; 访问第一个元素
MOV AL, [BX+1] ; 访问第二个元素
- 变址寻址:
MOV SI, 0
MOV AL, AA_1[SI] ; 访问第一个元素
INC SI
MOV AL, AA_1[SI] ; 访问第二个元素
- 基址加变址寻址:
MOV BX, OFFSET AA_1
MOV SI, 0
MOV AL, [BX+SI] ; 访问第一个元素
INC SI
MOV AL, [BX+SI] ; 访问第二个元素
- 基址加变址加位移寻址:
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
程序逻辑流程图

注意事项
- 当 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