我必须在汇编中编写一个程序,它读取一个数字并打印一个半金字塔的数字。
即:阅读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
答案 0 :(得分:3)
要使用另一条loop
指令创建内部循环,必须首先保存外部循环的状态,因为loop
条指令都使用cx
。
在内循环到期后,您必须恢复所述状态
如果不这样做将使外循环的计数器无效,特别是它将永远循环。
您可以在任何地方保存外循环计数器,但是如果您有备用寄存器,例如bx
,请使用它。
如果你没有这样的寄存器,你总是可以使用堆栈(push
和pop
),但除了引入在每个循环中断条件下保持堆栈平衡的约束之外,它还会访问记忆和使检索外部计数器更笨拙。
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位寄存器,使用cl
和ch
用于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/pop
和ah
值(并修改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
(换行),49
,32
(空格),{{1} }(数字2),50
,13
]。
这看起来并不明显&#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;有意义的修复或错误消息很酷。)