8086汇编递归实现十六进制转十进制前缀输出问题求助
8086汇编递归子程序问题排查与修复
作为工科学生完成作业时遇到8086汇编难题,耗时10小时未解决。需求是编写递归子程序,通过栈接收16位无符号十六进制数,将其转为十进制并按前缀逐行输出(例如输入
B26Eh需输出45678、4567等)。现有代码存在两个问题:
- 调试时递归返回后程序跳转至内存随机位置
- 修复部分问题后,无法在递归回溯时正确获取余数DX
原代码:
.model small .data .stack 100h .code START: ; Setting screen data into ES register MOV AX, 0B800h MOV ES, AX ; Setting the data segment MOV AX, @data MOV DS, AX ; Screen offset MOV CX, 0d MOV BX, 340h ; Testing result MOV AX, 746FH PUSH AX CALL numPrefix ; Exit and print both runs MOV AX, 4C00h INT 21h numPrefix PROC ; Save registers PUSH BP PUSH DX MOV BP, SP MOV AX, [BP+6d] ; Base condition CMP AX, 0 JE baseCase ; Else, divide the input by 16d to get the quotient and remainder PUSH CX ; Save print address mov cx, 10 ; Divisor xor dx, dx ; Clear dx for the quotient div cx ; Divide ax by cx, quotient in ax, remainder in dx POP CX ; Call numPrefix recursively with the quotient push ax ; Push the quotient onto the stack call numPrefix ; PRINT LOGIC ################################################################################# ; Now we print what we got for each row in the recursion MOV DH, 2Eh SUB BX, 0A0h PUSH BX PUSH CX CMP CX, 0d JE equalZero printLoop: ; For each row in the recursion, print the appropriate row of numbers MOV SI, BX MOV DI, [BX+0A0h] PUSH AX MOV AX, ES:[SI] MOV ES:[DI], AX POP AX ADD BX, 1 loop printLoop equalZero: POP CX ; Add new member ADD BX, 1 MOV ES:[BX], DX POP BX INC CX ; PRINT LOGIC ################################################################################# ; Finish and restore registers ; Also Base case where we only print and not do recursion again baseCase: pop DX pop BP ret 2 numPrefix ENDP END START
问题根源分析
递归返回跳转异常:
- 子程序仅保存
BP和DX,但递归过程中修改了BX、CX等全局寄存器,未对这些寄存器做现场保存,导致递归返回后寄存器状态被破坏,引发执行流异常。 - 打印逻辑直接修改
BX、CX,且未在子程序入口完整保存,递归调用时这些寄存器的值被后续递归实例覆盖。
- 子程序仅保存
回溯时余数DX丢失:
div cx得到的余数存于DX,但递归调用numPrefix时,子程序会执行push DX/pop DX操作,导致当前余数被覆盖,回溯时无法获取正确值。
修复后的代码
.model small .data .stack 100h .code START: ; 设置显存段 MOV AX, 0B800h MOV ES, AX ; 设置数据段 MOV AX, @data MOV DS, AX ; 初始化屏幕偏移和计数器 MOV CX, 0d MOV BX, 340h ; 初始输出位置(第5行左右) ; 测试:746FH = 29711,应输出29711、2971、297、29、2 MOV AX, 746FH PUSH AX CALL numPrefix ; 程序退出 MOV AX, 4C00h INT 21h numPrefix PROC ; 完整保存现场:递归中用到的寄存器全部入栈 PUSH BP MOV BP, SP PUSH BX PUSH CX PUSH DX MOV AX, [BP+10] ; 栈结构:BP+2=DX, BP+4=CX, BP+6=BX, BP+8=返回地址, BP+10=传入参数 ; 递归终止条件:AX为0时直接返回 CMP AX, 0 JE baseCase ; 除以10,得到商(AX)和余数(DX) MOV CX, 10 XOR DX, DX DIV CX ; 保存当前余数到栈(递归返回后取出使用) PUSH DX ; 递归调用:传入商作为新参数 PUSH AX CALL numPrefix ; 回溯时取出之前保存的余数 POP DX ; -------------------------- 打印逻辑 -------------------------- ; 1. 复制上一行的所有数字到当前行 SUB BX, 0A0h ; 上一行起始位置(每行80*2=160=0A0h字节) MOV SI, BX MOV DI, BX + 0A0h ; 当前行起始位置 MOV CX, [BP+4] ; 取出递归前的CX值(已打印数字个数) JCXZ skip_copy ; CX为0时无需复制 copy_loop: MOV AX, ES:[SI] MOV ES:[DI], AX ADD SI, 2 ; 每个字符占2字节(ASCII+显示属性) ADD DI, 2 LOOP copy_loop skip_copy: ; 2. 在当前行末尾添加新的余数(转为ASCII) MOV AL, DL ADD AL, 30h ; 数字转ASCII码 MOV AH, 07h ; 黑底白字显示属性 MOV ES:[DI], AX ; 更新计数器和下一行起始位置 INC WORD PTR [BP+4] ; CX自增(已打印数字个数+1) MOV BX, DI + 2 ; 下一行起始位置为当前行末尾+2 baseCase: ; 恢复现场 POP DX POP CX POP BX POP BP RET 2 ; 清理栈上的16位参数 numPrefix ENDP END START
修复说明
- 寄存器完整保存:子程序入口保存
BP、BX、CX、DX,确保递归过程中寄存器状态不被破坏,解决跳转异常问题。 - 余数栈存储:递归调用前将余数
DX压入栈,回溯时弹出,保证能获取当前层级的正确余数。 - 显存访问修正:针对VGA显存双字节(ASCII+属性)的存储结构,修复单字节操作导致的显示异常。
- 递归终止逻辑优化:AX为0时直接返回,避免执行不必要的打印逻辑。
内容的提问来源于stack exchange,提问作者Zig302
相关产品推荐
相关产品推荐

