在x86汇编中将整数打印到控制台

9
当我在16位汇编中添加两个值时,将结果打印到控制台的最佳方法是什么?
目前我有以下代码:
;;---CODE START---;;
mov ax, 1   ;put 1 into ax
add ax, 2   ; add 2 to ax current value
mov ah,2  ; 2 is the function number of output char in the DOS Services.
mov dl, ax ; DL takes the value.
int 21h    ; calls DOS Services

mov ah,4Ch   ; 4Ch is the function number for exit program in DOS Services.
int 21h      ; function 4Ch doesn't care about anything in the registers.
;;---CODE END---;;

我认为dl值应该是ASCII码,但我不确定如何将加法后的ax值转换为ASCII。


1
“使用DOS显示数字”(https://dev59.com/jaTja4cB1Zd3GeqPFa38)有更复杂的答案,包括仅使用16位寄存器处理32位整数。Sep在这里的新答案只是最简单的形式,适用于1位或16位整数,存储到缓冲区的末尾。 - Peter Cordes
5个回答

11
你基本上想要将数字除以10,打印余数(一位数字),然后用商重复此过程。
    ; assume number is in eax
    mov ecx, 10

loophere:
    mov edx, 0
    div ecx

    ; now eax <-- eax/10
    ;     edx <-- eax % 10

    ; print edx
    ; this is one digit, which we have to convert to ASCII
    ; the print routine uses edx and eax, so let's push eax
    ; onto the stack. we clear edx at the beginning of the
    ; loop anyway, so we don't care if we much around with it

    push eax

    ; convert dl to ascii
    add dl, '0'

    mov ah,2  ; 2 is the function number of output char in the DOS Services.
    int 21h    ; calls DOS Services

    ; now restore eax
    pop eax

    ; if eax is zero, we can quit

    cmp eax, 0
    jnz loophere

作为附注,你的代码有一个错误就在这里:
mov ax, 1   ;put 1 into ax
add ax, 2   ; add 2 to ax current value
mov ah,2  ; 2 is the function number of output char in the DOS Services.
mov dl, ax ; DL takes the value.

你将数字2放入ah,然后将ax放入dl。实际上,在打印之前你已经废弃了ax
此外,由于dl宽度为8位,而ax宽度为16位,因此存在大小不匹配的问题。
你应该交换最后两行并解决大小不匹配的问题。
mov ax, 1   ;put 1 into ax
add ax, 2   ; add 2 to ax current value

mov dl, al ; DL takes the value.
mov ah,2  ; 2 is the function number of output char in the DOS Services.

1
我可能错了,但我认为你的代码会倒序打印数字。例如:123 会打印 _321_。 - Gark Garcia
这将以相反的顺序打印,最不重要的数字先打印。从末尾开始向后工作,像*p-- = digit这样存储到缓冲区中,以生成一个字符串。或者参见DOS显示数字 - Peter Cordes
@PeterCordes:我认为这就是 Sidon 在下面他的答案中写的内容:https://dev59.com/zlLTa4cB1Zd3GeqPXBNP#5812104 - Nathan Fellman
2
是的。我认为在评论这里并转向查看其他答案后,我也注意到了同样的事情。这个答案已被接受并得到了显著的赞同,所以它应该仍然会被修复,因此我决定保留那个评论。 - Peter Cordes
@PeterCordes,这个答案似乎使用了32位寄存器,而问题标记为16位,你觉得呢? - Erik Eidt
1
@ErikEidt:在386或更高版本上,32位寄存器可以在16位模式下访问。该问题没有指定8086,一些未来的读者可能没有相同的限制。因此,这不是一个绊脚石,与倒序打印数字不同,这就是我为什么对它进行了负面评价的原因。 - Peter Cordes

7

仅修复了@Nathan Fellman代码的顺序

PrintNumber proc
    mov cx, 0
    mov bx, 10
@@loophere:
    mov dx, 0
    div bx                          ;divide by ten

    ; now ax <-- ax/10
    ;     dx <-- ax % 10

    ; print dx
    ; this is one digit, which we have to convert to ASCII
    ; the print routine uses dx and ax, so let's push ax
    ; onto the stack. we clear dx at the beginning of the
    ; loop anyway, so we don't care if we much around with it

    push ax
    add dl, '0'                     ;convert dl to ascii

    pop ax                          ;restore ax
    push dx                         ;digits are in reversed order, must use stack
    inc cx                          ;remember how many digits we pushed to stack
    cmp ax, 0                       ;if ax is zero, we can quit
jnz @@loophere

    ;cx is already set
    mov ah, 2                       ;2 is the function number of output char in the DOS Services.
@@loophere2:
    pop dx                          ;restore digits from last to first
    int 21h                         ;calls DOS Services
    loop @@loophere2

    ret
PrintNumber endp

2
第一个循环中的 push ax / pop ax 是没有意义的,而且与在循环中推入数字时混合使用会分散注意力。add dl,'0' 不会影响 AX。(您将 int 21h 调用移出了第一个循环与 Nathan 的版本不同,但没有去除 AX 的保存 / 恢复)。将数字推入堆栈的两个循环是一种方法,但向后存储到缓冲区通常很好,就像 Sep 的答案所示。 - Peter Cordes

4
基本算法如下:

基本算法如下:

divide number x by 10, giving quotient q and remainder r
emit r
if q is not zero, set x = q and repeat 

请注意,这将按相反的顺序产生数字,因此您可能希望用一些东西替换“emit”步骤,以便存储每个数字,以便稍后可以反向迭代存储的数字。
另外,请注意,要将介于0和9(十进制)之间的二进制数转换为ASCII码,只需将'0'的ASCII码(即48)加上该数字。

2
mov dl, ax

这不起作用,因为dlax具有不同的位大小。你想做的是创建一个循环,在其中将16位值除以10,将余数记住在堆栈上,然后以整数除法结果继续循环。当你达到结果为0时,逐个清除堆栈数字,将它们加上48变成ASCII数字,然后打印它们。

1

被接受的答案是错误的,因为它显示了结果的反向!这就是为什么我今天写这个答案的原因。我的答案展示了更好的技术,可以一次性输出结果(通过DOS函数09h)。

你的代码

mov ah,2  ; 2 is the function number of output char in the DOS Services.
mov dl, ax ; DL takes the value.
int 21h    ; calls DOS Services

int 21h指令之前的指令中始终加载DOS功能号。
始终检查大小:不能从16位寄存器移动到8位寄存器。

我认为dl的值应该是ASCII码,但我不确定如何将加法后的ax值转换为ASCII码。

您的数字允许使用DOS.PrintCharacter函数。要将值3转换为字符“3”,只需添加48即可在DL中获得ASCII码。

当我在16位汇编中添加两个值时,将结果打印到控制台的最佳方法是什么?

单个数字结果的示例

; Sum
  mov  al, 1
  add  al, 2

; Print
  add  al, '0'
  mov  dl, al
  mov  ah, 02h        ; DOS.PrintCharacter
  int  21h

; Exit
  mov  ax, 4C00h      ; DOS.TerminateWithExitcode
  int  21h

多位数运算结果的解决方案

; Sum
  mov  ax, 7346
  add  ax, 913

; Print
  mov  bx, Buffer + 5 ; Position of the mandatory '$' terminator
  mov  cx, 10
More:
  xor  dx, dx
  div  cx
  dec  bx
  add  dl, '0'        ; Convert to ASCII
  mov  [bx], dl
  test ax, ax
  jnz  More
  mov  dx, bx
  mov  ah, 09h        ; DOS.PrintString
  int  21h

; Exit
  mov  ax, 4C00h      ; DOS.TerminateWithExitcode
  int  21h

; Storage
Buffer: db '.....$'  

NASM使用mov bx, Buffer + 5指令
MASM使用mov bx, OFFSET Buffer + 5指令


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接