汇编:字符串的输入

程序的处理思路:

1、调用int 16h读取键盘输入;

2、如果不是字符:

2.1 如果是退格键,从字符栈中弹出一个字符,显示字符栈中所有字符;继续执行"读取键盘输入"。

2.2 如果是Enter键,向字符栈中压入0,返回。

3、如果是字符键:字符入栈,显示字符栈中的所有字符,继续执行"读取键盘输入"。

复制代码
assume cs:code ,ds:data
;定义栈空间
data segment
	db 32 dup(?)
data ends

code segment
start:
	mov ax,data
	mov ds,ax
	mov si,0
	mov dh,12		; 显示行:第12行
	mov dl,20		; 显示列:第20列
	call getstr
return:
	mov ax,4c00h
	int 21h
	
getstr:
	push ax

getstrs:
	mov ah,0
	int 16h   ;阻塞,等等键盘输入
	
	cmp al,20h				; 判读是否为可显示字符(ASCII≥20H)
	jb nochar				; 非可显示字符,跳转到nochar处理
	;字符al入栈
	mov ah,0
	call charstack
	;显示栈中的字符
	mov ah,2
	call charstack
	
	jmp getstrs
	
;处理非字符
nochar:
	cmp ah,0eh		;退格键的扫描码
	je backspace
	cmp ah,1ch		;回车键的扫描码
	je enter
	jmp getstrs
	
;对退格键、回车键的处理
backspace:
	;字符出栈
	mov ah,1
	call charstack
	;显示栈中的字符
	mov ah,2
	call charstack
	
	jmp getstrs
	
enter:
	mov al,0
	;0字符入栈
	mov ah,0
	call charstack
	;显示栈中的字符
	mov ah,2
	call charstack
	
	pop ax
	ret 		;getstr结束
	
;功能子程序实现
charstack:
	jmp short charstart
	table dw charpush ,charpop,charshow		; 功能跳转表(ah=0/1/2对应入栈/出栈/显示)
	top dw 0     						; 栈顶指针(初始=0,记录栈中字符数)	记录栈中有多少个字符的计数器
charstart:
	push bx
	push dx
	push di
	push es
	
	; 功能分发:ah=0(入栈)/1(出栈)/2(显示)
	cmp ah,2
	ja sret			; ah>2,直接退出
	mov bl,ah
	mov bh,0
	add bx,bx				; bx=ah*2(dw占2字节,计算跳转表偏移)
	jmp word ptr table[bx]		; 跳转到对应功能
	
charpush:
	mov bx,top 
	mov [si][bx],al			;AL是键盘读入的字符的ASCII码
	inc top
	jmp sret
	
charpop:
	cmp top,0
	je sret
	dec top
	mov bx,top
	mov al,[si][bx]
	jmp sret
charshow:
	;在dh行dl列显示
	mov bx,0B800H
	mov es,bx
	mov al,160
	mov ah,0
	mul dh			;在代码第12行,dh=12			;此处语法默认mul dh = ax = al*dh
	mov di,ax		;示例:DH=12 → AX=160×12=1920(即 0780H),表示第 12 行的起始偏移是 1920 字节
					;将行偏移存入 DI,作为显存地址的 "行基准"。本例为第12行的首地址
					
	add dl,dl		;代码第13行,显示dl=20,dl+dl=40
	mov dh,0		;将 DX 的高 8 位清零,确保 DX 仅为列偏移的结果(避免 DH 原有值干扰);
	add di,dx		;这是加法计算,相当于在di=12行的基础上,再加40,即1920+40=1960字节,也就是第 12 行第 20 列的起始位置
	
	mov bx,0
	
charshows:
	cmp bx,top 
	jne noempty					;当 bx ≠ top 时,跳转到 noempty 标签(继续显示栈中字符)。
	mov byte ptr es:[di],' '
	jmp sret
	
noempty:
	mov al,[si][bx]
	mov es:[di],al
	mov byte ptr es:[di+2],' '
	inc bx
	add di,2
	jmp charshows
	
sret:
	pop es
	pop di
	pop dx
	pop bx
	ret
	
code ends
end start

关键知识点:

一、为什么是20H?

在汇编代码中 cmp al, 20h 是判断输入字符是否为可显示的 ASCII 字符 ,核心原因是:20H(十进制 32)是 ASCII 表中第一个 "可显示字符(空格)" 的编码,而小于20H的 ASCII 码均为不可显示的控制字符

1、ASCII 码的分类(核心依据)

ASCII 码分为两类关键区间,这是判断的核心逻辑:

ASCII 范围 十进制 类型 示例
0~31 0~20H 控制字符(不可显示) 回车 (0DH)、换行 (0AH)、退格 (08H)、ESC (1BH) 等
32~127 20H~7FH 可显示字符 空格 (20H)、数字 (30H~39H)、字母 (41H~5AH/61H~7AH)、符号等

二、为什么使用al,20h中的AL呢?

明确 int 16h (ah=0) 的返回值分工

调用 BIOS 键盘中断 int 16h (ah=0) 后,CPU 会把键盘输入的两类关键信息存入 AX 寄存器,分工非常清晰:

寄存器 存储内容 核心作用 示例(退格键)
AL 字符的 ASCII 码 表征 "字符本身是什么" 退格的 ASCII 是 08H(不可显示)
AH 键盘按键的扫描码 表征 "按的是哪个物理键" 退格键的扫描码是 0EH

简单说:

  • AL 回答 "这个键对应的字符是什么"(ASCII 是字符的 "身份标识");
  • AH 回答 "按的是键盘上哪个键"(扫描码是按键的 "硬件标识")。

三、cmp与jb的配合使用

指令 功能说明
cmp al, 20h 比较 AL 寄存器的值与 20H(ASCII 空格符的编码),本质是执行 AL - 20H,仅修改标志位(不保存结果)。
jb nochar jb = Jump if Below(无符号小于则跳转),检测 CF(进位标志)是否为 1:- 若 AL < 20HCF=1 → 跳转到 nochar;- 若 AL ≥ 20HCF=0 → 不跳转,执行下一条指令。

四、cmp与ja的配合使用

指令 功能说明
cmp ah, 2 比较指令:执行 AH - 2 的运算(不保存结果),仅修改标志寄存器(CF/OF/ZF 等)
ja sret 条件跳转指令:无符号数大于则跳转 (Jump if Above),跳转到sret标签

五、jmp与jne的配合使用

指令 全称 类型 功能说明
jmp Jump 无条件跳转 直接跳转到指定标签 / 地址,必然执行跳转,无任何条件限制
jne Jump if Not Equal 条件跳转 仅当标志寄存器中 ZF=0(即 "比较结果不相等")时,才跳转到指定标签 / 地址

常见搭配

  • cmp a, b + jne label → 如果 a ≠ b,跳转

六、mul dh的说明解释

mul dh 是 x86 汇编语言中的一条指令,用于无符号乘法。下面是对这条指令的详细解释:


指令格式:

复制代码
mul source

其中 source 是一个 8 位、16 位、32 位或 64 位的寄存器或内存操作数(取决于当前操作模式)。


针对 mul dh 的具体说明:

  • dh 是 8 位寄存器,是 dx 寄存器的高字节。
  • 执行 mul dh 表示将 AL (8 位累加器)中的值与 DH 中的值相乘。
  • 结果是一个 16 位无符号整数 ,存储在 AX 寄存器中。

数学表达:

复制代码
AX = AL × DH   (无符号乘法)

七、去掉si应该也不影响程序运行吧?

结论先行 :去掉SI(直接删除mov si,0,并把所有[si][bx]改成[bx]),程序在逻辑上仍能运行 ,但会丧失SI带来的灵活性,且违背 8086 汇编的寻址设计规范 ------ 本质是SI=0时,[si][bx]等价于[bx],但SI的存在是为了 "适配通用场景",而非 "当前简单场景的必需"。

1、为什么去掉 SI 不影响当前程序运行?

核心原因是代码中SI被固定初始化为 0,且全程未修改:

asm

复制代码
start:
    mov ax,data
    mov ds,ax
    mov si,0  ; SI=0,全程不变
    ...
charpush:
    mov bx,top
    mov [si][bx],al  ; SI=0 → 等价于[bx] = al
    ...
charpop:
    mov bx,top
    mov al,[si][bx]  ; SI=0 → 等价于al = [bx]
    ...
charshow:
    mov al,[si][bx]  ; SI=0 → 等价于al = [bx]

8086 的[si][bx]基址 + 变址寻址 ,公式为:物理地址 = DS×16 + SI + BXSI=0时,公式简化为:物理地址 = DS×16 + BX这与直接使用[bx](仅变址寻址)的结果完全一致,因此去掉SI、将[si][bx]改为[bx],程序的栈读写逻辑不会变化,仍能正常入栈、出栈、显示。

2、但 SI 的存在绝非多余(为什么代码要保留 SI?)

虽然当前场景下SI=0看似无用,但它是为代码的可扩展性和规范性设计的,核心价值体现在:

2.1. 支持 "多栈 / 栈偏移存储"(扩展性)

如果后续需要在data段中定义多个栈(如 "输入栈"+"备份栈"),或让字符栈不从data:0开始存储,只需修改SI的值即可,无需重构核心寻址逻辑:

复制代码
; 示例:字符栈从data:32开始(前32字节用于其他数据)
mov si,32  ; 仅修改SI,栈操作逻辑([si][bx])完全不变

若去掉SI,则需修改所有[bx][bx+32],代码耦合度高、易出错。

相关推荐
我在人间贩卖青春5 天前
汇编之伪指令
汇编·伪指令
我在人间贩卖青春5 天前
汇编之伪操作
汇编·伪操作
济6175 天前
FreeRTOS基础--堆栈概念与汇编指令实战解析
汇编·嵌入式·freertos
myloveasuka5 天前
汇编TEST指令
汇编
我在人间贩卖青春5 天前
汇编编程驱动LED
汇编·点亮led
我在人间贩卖青春5 天前
汇编和C编程相互调用
汇编·混合编程
myloveasuka6 天前
寻址方式笔记
汇编·笔记·计算机组成原理
请输入蚊子6 天前
《操作系统真象还原》 第六章 完善内核
linux·汇编·操作系统·bochs·操作系统真像还原
myloveasuka6 天前
指令格式举例
汇编·笔记·计算机组成原理
我在人间贩卖青春7 天前
汇编之分支跳转指令
汇编·arm·分支跳转