Assembly 在汇编中显示时间

Assembly 在汇编中显示时间,assembly,time,x86-16,emu8086,x86,Assembly,Time,X86 16,Emu8086,X86,您好,我正在尝试显示实际时间小时/分钟/秒这是我的代码示例: 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 在这里,您可以查看控制台显示的内容 您需要一个打印例程将字节打印为数字,而不是将它们作为字符直接写入屏幕。幸运的是,由于您只需要处理0到59之间的值,并且需

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

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
在这里,您可以查看控制台显示的内容


您需要一个打印例程将字节打印为数字,而不是将它们作为字符直接写入屏幕。幸运的是,由于您只需要处理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

您试图将
CH
CL
DH
寄存器中的值(时间)显示为aschi字符

假设您的
CH
包含值15h

现在,当您将此值放入
AL
并调用
int 10h
时,它将显示与值15h匹配的aschi字符。如果要显示15小时的十进制数字,则必须调用一个显示例程,该例程将中断寄存器中的值并提取其中的数字进行显示,而不仅仅是该值中的aschi表示

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

霍布斯的答案说明了如何做到这一点

是另一个教程

请参见说明集参考手册的标签,以及参考资料和教程的许多良好链接


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

这是@hobbs的print2Digits函数的优化和错误修复版本。(我在他的回答中也修正了版本,所以它也是正确的,但保留了对这个版本的优化)

因为我们使用了
div
将一个整数拆分为两个以10为基数的数字,所以我们需要
ah
为零。i、 e.股息在AX中,而不仅仅是所有可能的垃圾在AH中。我们可以将
cbw
mov-ah,0
保存,如果调用方将
movzx-ax,ch
或某个值保存为零
ah

(除了8086没有
movzx
,所以您实际上需要
xor ax,ax
/
mov al,ch

有一个DOS系统调用来打印整个字符串,所以您可以将字符存储到一个小缓冲区中,然后一次打印所有字符,就像我在这里所做的那样。有关缓冲区函数中更通用的int->string或中的其他多位数链接,请参见


还可以使用AL(而不是AX)除以10,从而避免了先将AH归零的需要。在当前的Intel和AMD CPU上,它比div r8快一点。但是,它将结果放在
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,8
div
在AX中以正确的顺序生成它们


对于32位地址大小可用的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位的位移,因此它在代码大小上损失很大,但它确实保存了一条指令

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


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

是2:14:02吗?:)是,如何获得数字输出我已经将emu8086添加到标签中,因为您发布的屏幕输出是针对该模拟器的。我不太明白,我必须在哪几行调用函数print2digits?(当我写move AX时,也会得到错误的参数。)有趣的是,过时的
AAM
指令是将
al
除以10的快捷方式。它类似于通过直接除数进行的
div
,输入仅来自
al
,而不是
ax
。它实际上比Haswell上的
divR8
稍快一些。您可以通过在div之后执行添加ax,0x3030,将两个数字转换为带on insn的ASCII来优化代码。但是有一个问题:
DI
是一个16位寄存器,所以实际上您正在执行
div r16
,将
dx:ax
除以
DI
。而
movdi,ah
将无法组装。
edx
ecx
是暂存寄存器的良好选择,在大多数abi中被调用阻塞。它们具有可在64位模式之外访问的字节组件。(在64位模式下,
DIL
RDI
的低位8,但需要REX前缀才能访问)。32位寄存器与受保护模式分开,即使它们都是用386引入的。您可以在16位模式下使用它们,并带有操作数大小前缀。我的代码应该可以与
位16
组合在一起。(有趣的是,我刚刚发现286有一个叫做的东西。通常的意思是386版本,默认操作数大小是32位。)@SepRoland:oops!我首先考虑的是最小的数字,而不是打印顺序。我可能也对玩
aam
感到困惑,因为我一直认为它就像
div
,但实际上它把商放在

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