You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

核心错误原因

  1. 多字符数字的ASCII处理错误:string和stringainitial中,10及以上的数字是双ASCII字符(比如"10"占2字节),但程序把它们当成单字节处理。对单字节减"0"时,10会被拆成'1'(ASCII 49)和'0'(ASCII 48),得到的数值完全错误,导致索引计算混乱。
  2. 置换表的存储格式错误: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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.18 11:37:33