使用一个变量正确显示消息输出,同时保持其位置

Correctly display the message output using one variable while retaining their position

美好的一天,亲爱的程序员,

我们的老师布置了一个从 5 到 0 倒计时的程序。
我的大多数同学使用许多变量来显示输出。它通过像下面给定的代码一样编码来工作。

问题是代码看起来很糟糕并且创建了太多额外的 LOC。

问题是如何缩短我的代码?

我不喜欢为此使用很多变量并且一个一个地调用每个变量并且多次调用光标位置。我只发布了一部分代码,因为代码太长了。

这是我的代码

csrPos macro x1:REQ, y1:REQ
       mov ah, 02h
       mov bh, 0
       mov dl, x1
       mov dh, y1
       int 10h
       endm

prnstr macro msg
    mov ah, 9
    mov dx, offset msg
    int 21h
    endm

.model small
.stack 100h
.data
       ; Five message with light color green
       msgFive0 db 27, "[2;32;40m$"
       msgFive1 db "ÛÛÛÛÛ$"
       msgFive2 db "ÛÛ   $"
       msgFive3 db "ÛÛ   $"
       msgFive4 db "ÛÛÛÛ $"
       msgFive5 db "   ÛÛ$"
       msgFive6 db "   ÛÛ$"
       msgFive7 db "ÛÛ ÛÛ$"
       msgFive8 db "ÛÛ ÛÛ$"
       msgFive9 db " ÛÛÛ $"
.code
       mov ax, @data
       mov ds, ax

       csrPos 38, 7
       prnstr msgfive0
       csrPos 38, 8
       prnstr msgfive1
       csrPos 38, 9
       prnstr msgfive2
       csrPos 38, 10
       prnstr msgfive3
       csrPos 38, 11
       prnstr msgfive4
       csrPos 38, 12
       prnstr msgfive5
       csrPos 38, 13
       prnstr msgfive6
       csrPos 38, 14
       prnstr msgfive7
       csrPos 38, 15
       prnstr msgfive8
       csrPos 38, 16
       prnstr msgfive9

       mov ah, 4ch
       int 21h

END

输出:
这是我 运行 时该程序的输出。正如我们的老师告诉我们做的那样,它看起来已经不错了。 Five output

我想做的就是这样。

.data
       ; Five message with light color green
       msgFive db 27, "[2;32;40m$"
                db "ÛÛÛÛÛ$"
                db "ÛÛ   $"
                db "ÛÛ   $"
                db "ÛÛÛÛ $"
                db "   ÛÛ$"
                db "   ÛÛ$"
                db "ÛÛ ÛÛ$"
                db "ÛÛ ÛÛ$"
                db " ÛÛÛ $"


.code
       mov ax, @data
       mov ds, ax

       csrPos 38, 7
       prnstr msgfive

       mov ah, 4ch
       int 21h
END

如何在使用该代码保留输出的同时做到这一点?

抱歉我的英语不好。我正在使用 TASM 汇编器

嗯,你的总体想法不错,但你没有解释你遇到了什么问题没有实施它。

在我看来,PITA 之一就是那个宏 csrPos 38, 7(我真的很讨厌宏,尤其是在刚刚学习汇编的人的代码中,所以认为这有点偏颇,但真的......我也会尝试以事实的方式解释,所以你可以自己决定)。

这确实会立即移动下一个输出的输出位置,但是随后您将字符打印为一系列多个输出,因此您需要在...之间调整位置但是建议的代码 prnstr msgfive 没有简单的想法之前的 csrPos 宏确实设置了它。您可以通过使用 BIOS 服务在打印前实际读取当前位置、打印字符、调整位置并执行 csrPos 来解决 prnstr 中的问题……这个想法应该立即感觉像 "code smell"(尽管如果你没有更好的想法,至少以 "wrong" 的方式编写它通常会有所帮助,这样你就可以看到自己的结果来源,并以新的经验再次考虑它,这通常有助于找到出更好的解决方案)。

所以你可能想要的是像 prnstr msgfive, 38, 7 这样的东西,让 prnstr 自己处理位置,并从代码的主要部分删除 csrPos 38, 7...也为您节省 1LOC,它还消除了信息责任冲突,其中 prnstr 不知道应该在哪里打印它。

但是我会牺牲一些源代码的 LOC 来完全避免宏,而是做一些类似的事情(例如,如果你愿意,也可以随意选择不同的方式提供参数,比如不同的寄存器甚至基于堆栈的调用约定):

    ...
    mov  dx,38 + 7*256        ; position[38,7]
    mov  si,OFFSET char_five  ; memory address of font data
    mov  bl,10                ; light green color
    call print_char           ; call subroutine to output the character
    ...

现在 print_char 子例程,完成所有繁重的工作,拥有所有重要信息,因此它可以自行决定,when/how 它将修改输出位置,以及显示某些字符的任务现在主区块只有4个LOC,在IMO看来是合理的。


如何获得如何缩短代码的好主意..好吧,如果您查看原始代码(最好是在调试器的反汇编视图中),您会看到一系列相同的指令,重复而且,只是具有不同的即时值。这始终是一个标志,您可以将这些直接数据移动到某种数据结构中,并且只编写一次代码,使用某种 "loop" 来重复它与多个数据。

因此,您应该寻找代码中与其他代码不同且不断重复的重要部分 - 如果它超过 3 次,则可能值得将其转换为子例程。

此外,您还应该经常尝试查找可以实际计算的即时数据 - 例如,如果您想打印从 2 到 1000 的素数,您可以将完整列表作为文本放入源中(它会是最快的解决方案!),或者您可以将其编程为从整数 2 到 1000 的循环,测试每个是否为素数,然后输出它(以计算的代价计算较小的二进制数)。

有些数据通常更容易计算,然后在源代码中手动写入(例如“从第 38 列开始的行块的光标位置”),性能下降通常可以忽略不计(比如在您当前的任务中,你应该有 1 秒的延迟,所以无论你是在 10 微秒还是 15 微秒内显示数字都没有那么重要。

其他与 ASM 相关的保存一些 LOC 或使其更易于维护的方法:

  • 如果您知道您的子例程必须将位置放入 dhdl,并且您可以自由使用自己的自定义调用约定,那么没有理由不直接在 dx 中传递参数,因此子例程不需要 copy/move 参数值到 dh:dl 本身。

  • 使用子例程而不是宏,这样可以更轻松地为其他人查看源代码并且更容易调试,因为您在调试器中看到的内容与您写入源代码的内容更加相似。当你调试宏时,你会在调试器中以注入的形式看到它们,即它根本不像源代码,这使得它更加混乱(一旦你遇到特殊情况,使用宏是有意义的,你就会知道它,但此时你的宏更像是子程序,应该这样写)。 (同样当经验丰富的程序员审查你的代码时,他们知道 mov ax,10h 做什么,但他们不知道 csrPos 做什么,因为那不是 x86 指令,所以他们必须在代码之间来回切换他们正在审查和宏定义,因为他们不会立即记住它,但他们必须每次都考虑隐藏在宏中的每条指令,因为这些可能会对您的其他代码产生不必要的副作用,所以他们不能像 "ok, that does just change position, no need to bother with instructions" ...你自己也会感到困惑,几个月后你会检查那个来源)

您可以在此处查看我对其他类似问题的回答 TASM cursor position 以查看实现类似 print_char 子例程的特定方法,使用循环和数据定义使代码更完整 "general",适合打印"any"字符。它使子例程本身比您的 pos, print, pos, print, pos, print, ... 序列复杂得多,但您只需编写一次,只需调试一百次,然后就可以将它用于所有字符。它也更有趣,即使它需要更长的时间。你可以吹嘘你比那些使用繁琐的手动编写所有内容的人更 老练 程序员......;) :D