4

您好我试图显示实际时间小时/分钟/秒这是我的代码示例:

MOV AH, 2Ch
INT 21h

MOV AH, 0Eh

MOV AL, CH
INT 10h

MOV AL, 3Ah
INT 10h

MOV AL, CL
INT 10h

MOV AL, 3Ah
INT 10h

MOV AL, DH
INT 10h

ret

在这里您可以查看控制台显示的内容

在此处输入图像描述

4

3 回答 3

4

您需要一个打印例程将字节打印为数字,而不是将它们作为字符直接写入屏幕。幸运的是,您只需要处理 0 到 59 之间的值,而且您需要前导零,因此问题非常简单。假设要在 AX 中打印的值:

print2Digits:
    ;; input in AX (0-99)
    ;; clobbers AX and DX, save them if needed
    MOV   DL, 0Ah ; divide by: 10
    DIV   DL      ; first digit in AL (quotient), second digit in AH (remainder)
    MOV   DX, AX  ; save the digits
    ADD   AL, 30h ; ASCII '0'
    MOV   AH, 0Eh ; set up print
    INT   10h     ; print first digit.
    MOV   AL, DH  ; retrieve second digit
    ADD   AL, 30h
    INT   10h     ; print it
    RET
于 2016-05-10T05:37:16.123 回答
4

请参阅标签wiki以获取指令集参考手册,以及许多参考资料和教程的良好链接。


将整数拆分为 ASCII 数字需要足够的代码,您应该将其分解为函数。

这是@hobbs 的 print2Digits 函数的优化和修正版本。(我还在他的答案中修正了版本,所以它也是正确的,但留下了这个优化)。

print2Digits:
    ;; input in AL (0-99).  (Or preferably already zero-extended to AX so we can omit CBW)
    ;; clobbers AX and DX
    cbw             ; zero AH.  Sign-extending AL does the job because AL is only allowed to be 0-99.

    mov   dl, 10
    div   dl        ; quotient in AL(first (high) digit), remainder in AH(second (low) digit)

    add   ax, 0x3030  ; add '0' to al and ah at the same time.
    mov   dl, ah      ; save the 2nd digit

    mov   ah, 0x0E   ; BIOS call #: print single character
    int   0x10       ; print high digit first.  Doesn't clobber anything, so AH still holds 0x0E after
    mov   al, dl
    int   0x10       ; print the low digit 2nd
    ret

由于我们曾经div将一个整数拆分为两个 base10 数字,因此我们需要ah为零。即股息在 AX 中,而不仅仅是在 AH 中可能存在垃圾的 AL。我们可以将cbwor mov ah,0if the caller did movzx ax, chor something 保存为零ah

(除了 8086 没有movzx,所以你实际上想要xor ax,ax/ mov al, ch。)

有一个用于打印整个字符串的 DOS 系统调用,因此您可以将字符存储到一个小缓冲区中并一次打印它们,就像我在这个AMD64 Linux FizzBu​​zz中所做的那样。另请参阅如何在没有来自 c 库的 printf 的情况下在汇编级编程中打印整数?对于缓冲区函数中更通用的 int->string,或x86 标签 wiki中的其他多位数字链接


可以使用aam将 AL(而不是 AX)除以 10,从而避免需要先将 AH 归零。它比div r8当前的 Intel 和 AMD CPU 稍快。但是,它将结果放在与 相对的寄存器中div,这意味着 . 之后有额外的指令aam。这平衡了 和 的mov dl, 10节省cbw

print2Digits:
    ;; input in AL (0-99).  (Ignores AH because we use AAM instead of div)
    ;; clobbers AX and DX
    aam               ; like `div` by 10, but with the outputs reversed, and input from AL only
          ;; quotient in AH (high digit), remainder in AL(low digit).  (Opposite to div)

    add   ax, 0x3030  ; add '0' to al and ah at the same time.
    mov   dl, al      ; save the low digit
    mov   al, ah      ; print high digit first

    mov   ah, 0x0E    ; BIOS call #: print single character
    int   0x10        ; print first digit.  Doesn't clobber anything, so AH still holds 0x0E after
    mov   al, dl
    int   0x10        ; print second digit
    ret

即使我们想存储到一个字符串(并调用一个打印字符串函数或系统调用),我们也必须在将 AX 存储到内存之前交换 al 和 ah(例如xchg al,ah,或者在现代硬件上更有效,但需要186:)rol ax,8div在 AX 中以正确的顺序生成它们。


对于 32bit 地址大小可用的 386,我们可以节省一条指令:

lea   dx, [eax + 0x3030]   ; need a 32bit addressing mode to use eax as a source reg.  Adds '0' to both digits at once, with a different destination.
mov   al, dh               ; then get ready to print the high byte first

需要一个地址大小的lea前缀和一个 2 字节的 mod/rm,以及一个 32 位的位移,所以它在代码大小上损失惨重,但它确实节省了一条指令。

在 Sandybridge 系列 CPU 上,尤其是在写入后使用lea读取可能会更快。Haswell 和更高版本,但在 Intel pre-SnB 上,部分寄存器停顿将使其更好地使用具有单独 add 和 mov 指令的纯 16 位版本。eaxdivax

当然,如果您真的关心性能,您会使用乘法逆而不是实际除以 10。 而且您通常也不会编写进行传统 BIOS 调用的 16 位代码!

于 2016-05-10T06:54:23.603 回答
2

您试图在 中显示值(时间)CHCL并将DH其注册为 aschi 字符。

假设您CH包含值 15h。

现在,当您将此值放入AL并调用int 10h时,它会显示与值 15h 匹配的 aschi 字符。如果你想显示 15h 的十进制数字,那么你将不得不调用一个显示例程,它会破坏寄存器中的值并提取其中的数字来显示 - 而不仅仅是该值中的 aschi 表示。

假设您在寄存器中有十进制 85。现在您需要在屏幕“8”和“5”上打印。所以你必须将值 85 转换为 aschi-56("8") 和 aschii-53("5")。然后将它们依次放入寄存器,调用int 10两次显示“85”。

霍布斯的回答显示了如何做到这一点。

是另一个教程

于 2016-05-10T06:34:10.800 回答