8086汇编置换次数计算程序故障:ASCII值>16时功能失效
问题分析与修复方案
我写了一段8086汇编程序,需求是计算将初始数组恢复原状所需的置换次数,并展示每次置换的结果。但程序只在处理ASCII值≤16的数字时能正常运行,当数字ASCII值大于16时就失效了。我推测问题出在索引访问和ASCII转换的逻辑上,试过隔离8位操作,但只有数字≤16时有效。以下是完整代码:
;----------------------------------------------------------------- ; M A C R O ;----------------------------------------------------------------- display macro xxx push ax push dx mov dx, offset xxx mov ah, 9 int 21h pop dx pop ax endm ;----------------------------------------------------------------- ; Definizione costanti CR EQU 13 ; carriage return LF EQU 10 ; line feed DOLLAR EQU "$" ;----------------------------------------------------------------- ; PILA SEGMENT STACK 'STACK' ; stack' segment definition DB 64 DUP('STACK') ; stack is filled with the string "stack" ; per identificarlo meglio in fase di debug PILA ENDS ;----------------------------------------------------------------- ; DATI SEGMENT PUBLIC 'DATA' ; data segment definition CRLF db CR,LF,DOLLAR msg_p db "permutation:", DOLLAR msg_n db "number:",DOLLAR r db "0", DOLLAR ; the number of permutations look_up db "0", "4", "1", "5", "8", "12", "9", "13", "17", "21", "24" , "28", "25","29", "26", "30", "27","31","18", "22", "19", "23", "10", "14", "11", "15", "2", "6", "3", "7" ; permutation law string db "0","1","2" ,"3","4","5","6","7","8","9","10", "11","12","13","14","15","16","17","18","19","20","21","22","23","24","25","26","27","28","29","30","31", DOLLAR ; string Ill apply the permutation on stringainitial db "0","1","2" ,"3","4","5","6","7","8","9","10", "11","12","13","14","15","16","17","18","19","20","21","22","23","24","25","26","27","28","29","30","31", DOLLAR ; constant to check if the permutation changed the string back to the starting point DATI ENDS ;================================================================= CSEG1 SEGMENT PUBLIC 'CODE' MAIN proc far ASSUME CS:CSEG1,DS:DATI,SS:PILA,ES:NOTHING; initialization MOV AX, DATI MOV DS, AX XOR CX, CX ; using CX for indexes xor ax, ax ; ax as r counter MOV BX, OFFSET string mov si, offset stringainitial display string display CRLF cicle: mov dx, offset look_up ; I use the register multiple times, so Ill initialize it each cicle inc ax push dx push bx call permucalc pop bx pop dx display msg_p ; display string after permutation display string display CRLF xor cx,cx checkifokay: ; check if the string is the same as at the starting point add bx, cx add si, cx mov di, [bx] mov dx, [si] sub bx, cx sub si, cx cmp di,dx jne cicle cmp cx, 32 je exit inc cx jmp checkifokay exit: mov bx, offset r sub [bx], "0" add [bx], ax add [bx], "0" display msg_n display r display CRLF main endp ;----------------------------------------------------------------- ; permucalc proc push ax push bx push cx push dx push si push bp mov bp, sp mov bx, [bp+14] ; (stringa) mov si, [bp+16] ; (table) xor cx,cx cicleproc: cmp cx, 32 je terminate add bx, cx ; need this to access the correct index sub [bx], "0" ; to transform the number from ASCII mov ax, [bx] xor ah, ah add si, ax mov dx, [si] sub si, ax xor dh, dh mov [bx], dl sub bx, cx ; need to go back to the starting offset inc cx jmp cicleproc terminate: pop bp pop si pop dx pop cx pop bx pop ax ret endp
核心错误原因
- 多字符数字的ASCII处理错误:
string和stringainitial中,10及以上的数字是双ASCII字符(比如"10"占2字节),但程序把它们当成单字节处理。对单字节减"0"时,10会被拆成'1'(ASCII 49)和'0'(ASCII 48),得到的数值完全错误,导致索引计算混乱。 - 置换表的存储格式错误:
look_up存储的是ASCII字符串(比如"12"占2字节),但程序用16位寄存器DX读取,取到的是两个字节的组合值,而非实际需要的置换数值,进一步加剧索引错误。
修复方案
1. 改用数值存储替代ASCII字符串
把所有数据数组改成存储原始数值(0-31),彻底避免ASCII转换的混乱:
look_up db 0,4,1,5,8,12,9,13,17,21,24,28,25,29,26,30,27,31,18,22,19,23,10,14,11,15,2,6,3,7 ; 去掉DOLLAR,因为现在是数值数组,不是字符串 string db 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31 stringainitial db 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31
2. 修复置换逻辑的索引访问
现在直接用数值索引访问置换表,不需要ASCII转换,简化permucalc子程序:
permucalc proc push ax push bx push cx push si push bp mov bp, sp mov bx, [bp+12] ; string的偏移(栈布局调整:去掉了dx的push) mov si, [bp+14] ; look_up的偏移 xor cx,cx cicleproc: cmp cx, 32 je terminate mov al, [bx+cx] ; 取当前位置的数值 mov dl, [si+al] ; 取look_up中对应索引的置换值 mov [bx+cx], dl ; 存回string inc cx jmp cicleproc terminate: pop bp pop si pop cx pop bx pop ax ret endp
注意:栈布局调整了,因为去掉了
dx的push,所以参数的栈偏移从+14/+16变成了+12/+14。
3. 添加数值转ASCII的显示子程序
DOS中断只能显示ASCII字符串,所以需要写一个子程序把数值转换成可打印的ASCII格式:
; 输入:AL=要转换的数值,DI=存储结果的缓冲区(至少3字节空间) ; 输出:DI指向的缓冲区会被填充为数值的ASCII字符串,末尾加$ num2str proc push ax push bx push cx mov cx, 10 xor bx, bx ; 分解数字到栈中 num_loop: xor ah, ah div cx push ax inc bx cmp al, 0 jne num_loop ; 将栈中的数字转成ASCII并存储 mov cx, bx mov ah, "0" str_loop: pop ax add ah, al mov [di], ah inc di loop str_loop mov byte ptr [di], "$" ; 添加字符串结束符 pop cx pop bx pop ax ret num2str endp
然后在主程序中,显示string时需要循环调用这个子程序,逐个转换并显示每个数值:
; 示例:显示整个string数组 display_string proc push ax push bx push cx push di mov bx, offset string xor cx, cx display_loop: cmp cx, 32 je display_end mov al, [bx+cx] mov di, offset temp_buf ; 定义一个临时缓冲区:temp_buf db 3 dup(0) call num2str display temp_buf ; 显示分隔符(比如空格) mov dl, " " mov ah, 2 int 21h inc cx jmp display_loop display_end: pop di pop cx pop bx pop ax ret display_string endp
记得在DATI段添加临时缓冲区:
temp_buf db 3 dup(0) ; 存储单个数值的ASCII转换结果
4. 修复恢复原状的检查逻辑
现在数组是数值类型,直接逐字节比较即可:
checkifokay: mov al, [bx+cx] cmp al, [si+cx] jne cicle inc cx cmp cx, 32 je exit jmp checkifokay
总结
原来的错误本质是混淆了数值存储和ASCII字符串存储,单字符数字的巧合掩盖了逻辑漏洞。改成直接存储数值,单独处理显示时的ASCII转换,就能彻底解决多字符数字的索引错误问题。
内容的提问来源于stack exchange,提问作者Babboboncia
相关产品推荐
相关产品推荐

