在装配中打印半个金字塔的数字

时间:2017-01-15 11:27:10

标签: assembly x86 tasm

我必须在汇编中编写一个程序,它读取一个数字并打印一个半金字塔的数字。

即:阅读4

打印

 1
 1 2
 1 2 3
 1 2 3 4

我已经理解了如何读取数字,如何使用循环打印每行一个字符,但我必须使用内循环,我不知道如何初始化另一个控件。

.286
.model small
.stack 1024h
.data

.code

mov cx,5
mov bx,5

cosmin:

mov dl,31h
mov ah, 2h
int 21h

mov dl, 0Ah
int 21h

loop cosmin

end

我在这里尝试只为一个角色制作一个三角形,但我不知道如何增加每一行的值。

.286
.model small
.stack 1024h
.data

.code

mov cx,5
mov bx,5

cosmin:

mov dl,31h
mov ah, 2h
int 21h

mov dl, 0Ah
int 21h

loop cosmin

end

2 个答案:

答案 0 :(得分:3)

要使用另一条loop指令创建内部循环,必须首先保存外部循环的状态,因为loop条指令都使用cx
在内循环到期后,您必须恢复所述状态 如果不这样做将使外循环的计数器无效,特别是它将永远循环。

您可以在任何地方保存外循环计数器,但是如果您有备用寄存器,例如bx,请使用它。
如果你没有这样的寄存器,你总是可以使用堆栈(pushpop),但除了引入在每个循环中断条件下保持堆栈平衡的约束之外,它还会访问记忆和使检索外部计数器更笨拙。

loop不是that great instruction,而是限制性的和缓慢的 另外,对于您的计算 up 的问题更好,所以我完全避免使用手动管理的计数器。

;Assume SI holds the number n (>=1) of rows of the pyramid

mov cx, 1                  ;Outer counter (i)

_rows_loop:
 mov bx, 1                 ;Inner counter (j)
__cols_loop:

  ;Print number bx

  inc bx               ;Increment inner counter
  cmp bx, cx           ;If (j<i) keep looping
 jb _cols_loop

 ;Print new-line

 inc cx                ;Increment outer counter
 cmp cx, si            ;If (i<n) keep looping
jb _rows_loop

如果你的备用寄存器用完了,你总是可以使用堆栈,但要注意它会使循环中断代码变得复杂,因为你推送的所有内容必须总是通过pop来平衡。此外,它访问内存。

在极端情况下,您可以使用8位寄存器,使用clch用于cx,上面代码中的bx适用于适合的计数器。

我告诉你找到生成金字塔的算法的问题。

答案 1 :(得分:2)

  

我不知道如何增加每行的值。

好吧,每行都inc where-the-value-is-stored(要么在备用寄存器中,要么在内存中,如果备用寄存器用完了)。

保持在头部或评论摘要中,您当前的寄存器分配是什么,因此您知道哪些是备用的或哪些已用于某些内容。

确保您满足外部呼叫的要求,因为您可以轻松为自己的代码选择不同的注册,但您无法更改int 21h以获取{{1}中的服务号码因为你的DOS供应商已经实现了接受bh中的服务号码。因此要么为自己避免ah使用,要么使用保留/恢复模式(下面)。

尽量保持简单的事情,例如递增值为ah。装配实际上相当不错,如果你在头脑中保持清晰的图像,就非常简单的数值运算/步骤而言,你通常可以找到一个非常直接和简单的ASM指令组合来做到这一点而不是很多其他。通常没有别的。

如果您很难以简单的方式将一些需求映射到少量汇编指令,那么您的高级任务可能不会被分解为简单的步骤,因此请尝试将其细分一点,然后再试一次直接翻译成说明书。

loop rel8是为数不多的复杂x86指令之一,基本上就是这样做的:

inc

但它不会影响标志(dec cx (ecx in 32b mode, rcx in 64b mode) jnz rel8 在内部发生的是单个专用的东西,而不是字面上的两个原始dec + jnz指令),并且它在现代x86 CPU上人为地慢,以帮助一点遗产SW使用空dec + jnz循环来创建&#34;延迟&#34; (这是徒劳的,因为它对于这样的SW来说仍然太快了,并且#34;删除了#34;否则非常好的操作码用于未来的SW:/)。

因此,您可能希望更喜欢实际的两条指令&#34; loop $ dec cx&#34;结合实际编程,它将在现代x86 CPU上具有更好的性能。

在汇编CPU寄存器就像&#34;超级全局&#34;,即。每个CPU核心都有单jnz rel8个(在现代x86上内部不是这样,但从程序员的角度来看,这就是从外部看起来的行为)。

因此,如果您需要两个不同的值,例如counter1和counter2,则必须编写额外的额外代码,在需要时保留适当的cx值,并根据需要加载另一个。

例如,由cx完成的两个嵌套循环:

loop

或者,如果你缺少备用寄存器,你可以使用堆栈,&#34;天真&#34;方式是:

    mov cx,10
outer_loop:
    mov bx,cx    ; preserve outer counter in bx
    mov cx,5
inner_loop:
    ; some loop code
    loop inner_loop
    mov cx,bx    ; restore outer counter
    loop outer_loop

(C ++编译器会以不同的方式解决这个问题,在堆栈空间中分配局部变量,所以不是推/弹,而是直接使用 mov cx,10 outer_loop: push cx ; preserve outer counter mov cx,5 inner_loop: ; some loop code loop inner_loop pop cx ; restore outer counter loop outer_loop [sp+x]的相同内存点,从而节省了性能不会在每次使用时调整[bp-x],例如sp确实如此

但是如果你看看我的答案的前一部分,你应该能够找到不同的方法来解决使用两个计数器的嵌套循环 - 没有额外的保留/恢复指令。

但是,特定目标寄存器中的保留/恢复值的模式是您必须完全理解并能够在各种不同情况下使用的(即使嵌套循环不需要它),例如,如果您将阅读有关ah=2, int 21h的文档,您可以看到它只关注push/popah值(并修改dl)。例如,al是&#34;备用&#34;。

然后,如果您想输出两个字符:dh和空格,但您仍希望在主&#34;变量&#34;中以A结尾。 (在下一个例子中将是A),你可以这样做:

dl

最后,如果你有像你这样的任务并且解决方案不明显,其中一个推理步骤可以是&#34;回溯&#34;你想要什么。

你知道你想在屏幕上输出(init_part: mov dx,' '*256 + 'A' ; dh = ' ', dl = 'A' mov ah,2 ; output single char service ; some other init code, etc.. inner_part_somewhere_later: int 21h ; output dl to screen (initially 'A') xchg dl,dh ; preserves old "dl" and loads "dh" into it (swaps them) int 21h ; output dh to screen (space) xchg dl,dh ; restores 'A' in dl ; so *here* you can operate with 'dl' ; as some inner_part loop "variable" ; modifying it for another inner_part iteration =新行):

<NL>

想象一下底层到底意味着什么。当然有几种方法可以实现它(包括编写整行,在内存缓冲区中编写,而不是单个字符),但如果我坚持使用单个字符输出,那么这个想要的输出会转化为这种需求:

要将1<NL> 1 2<NL> 设为:int 21h, ah=2,请致电dl [49(数字1),13(回车),10(换行),4932(空格),{{1} }(数字2),5013]。

这看起来并不明显&#34; loopable&#34;,但是如果你想添加更多的行,那么模式&#34;数字+空格&#34;内循环会出现。你也可以&#34;作弊&#34;一点点,并在最后一个数字后输出一个无用的空格,对于普通用户来说它将是&#34;不可见&#34;。那时你应该能够回溯&#34;这个高级设计:

10

现在你可以尝试在头脑中运行几次,以验证输出是否真的是你想要的。

如果您有如此高级别的概述,如何实现所需的输出,您可以开始寻找如何很好地实现其特定步骤的方法。

如果您确实理解了此答案的每个部分,我认为将此算法重写为ASM指令非常容易。只需将代码中的注释保留在特定指令组之前。

然后,当您因为某些错误而进行调试时,您可以轻松地比较代码实际执行的操作与注释,应该执行的操作,找出差异并进行修复。

但是,所有时间比较你的代码的主要事情是屏幕定义的最终输出,无论何时你被卡住,比较你当前的输出与期望,发现一些差异,评估哪一个看起来最容易修复,并尝试解决它。如果没有发现更多的差异,那么您就可以完成&#34;尽管我强烈建议您再次查看代码,是否可以简化,以及它适用于角落案例(如&#34;如果用户输入字母而不是数字&#34; ,会发生什么情况。)

拥有能够正确处理每个角落案例的代码并不重要,但是你应该知道在每种情况下会发生什么,并确定这是否足够好&#34; #34;或不(根据经验,&#34;垃圾输入 - &gt;垃圾输出&#34;很好,&#34;垃圾输入 - &gt;崩溃或数据损坏&#34;并不酷,&#34 ;垃圾进入 - &gt;有意义的修复或错误消息很酷。)