我的主要关注的是布局。我见过的每个“基本”高级实现都有一个检查并针对每种情况一起打印。我发现在这里做同样的事情比较容易,但是我不确定这在汇编中是否也不太可读。 。不幸的是,我仍然会在每种情况下都这样做,因为我要增加一个寄存器(
CX
)并使用另一个寄存器(AX
)进行分红。对于这两者,我都可以坚持使用AX
,但这可能涉及保留当前计数器值的副本,这可能会使代码更加复杂。我想无论如何这不是什么大问题。使用的宏:
nwln
-打印换行符> PutStr
-打印定义的字符串PutInt
-打印16位整数值不必寻址宏;它们确实可以正常工作。
%include "macros.s"
.DATA
fizz_lbl: DB "Fizz", 0
buzz_lbl: DB "Buzz", 0
fizzbuzz_lbl: DB "FizzBuzz", 0
.CODE
.STARTUP
xor CX, CX ; counter
main_loop:
inc CX
cmp CX, 100
jg done
fizzbuzz_check:
mov AX, CX ; dividend = counter
mov BH, 15 ; divisor
div BH ; (counter / 15)
cmp AH, 0 ; counter divisible by 15?
je print_fizzbuzz ; if so, proceed with printing
jmp fizz_check ; if not, try checking for fizz
print_fizzbuzz:
PutStr fizzbuzz_lbl
nwln
jmp main_loop
fizz_check:
mov AX, CX ; dividend = counter
mov BH, 3 ; divisor
div BH ; (counter / 3)
cmp AH, 0 ; counter divisible by 3?
je print_fizz ; if so, proceed with printing
jmp buzz_check ; if not, try checking for buzz
print_fizz:
PutStr fizz_lbl
nwln
jmp main_loop
buzz_check:
mov AX, CX ; dividend = counter
mov BH, 5 ; divisor
div BH ; (counter / 5)
cmp AH, 0 ; counter divisible by 5?
je print_buzz ; if so, proceed with printing
jmp print_other ; if not, then can only display number
print_buzz:
PutStr buzz_lbl
nwln
jmp main_loop
print_other:
PutInt CX
nwln
jmp main_loop
done:
.EXIT
#1 楼
由于我们使用汇编语言进行此操作,因此比高级语言通常更有效地执行此操作是有意义的。否则,为什么还要麻烦汇编语言?这样说来,有很多方法可以使它效率更高得多。既然我们已经知道我们正在寻找可以被3、5或两者整除的数字,那么更有意义的是简单地为这两个数字保留倒数计数器。您的初始化当前显示为: xor cx, cx
可以很容易地扩展为:
xor cx, cx
mov bx, 0503h ; set bh = 5 counter, bl = 3 counter
然后代替除法,简单地递减:
inc cx
cmp cx, 100
jg done
; instead of this...
; dec bh
; dec bl
; cmp bx, 0
; per suggestion from @Chris Jester-Young use this:
sub bx, 0101h
je print_fizzbuzz
cmp bl, 0
je print_fizz
cmp bh, 0
je print_buzz
print_other:
自然地,各种
div
例程将不得不重置print_...
,bh
或同时重置这两者以及打印。改进格式化
通常来说,汇编语言代码不会以缩进代码的方式缩进。它线性得多,只有汇编语言语句或指令的缩进形式。使数字输出保持字符串形式,增加每个ASCII数字并发出字符串的效率更高,而不是将二进制寄存器的内容重复转换为字符串值。
评论
\ $ \ begingroup \ $
代替dec bh; dec bl,您只能使用sub bx 101h。然后,您可以直接jz print_fizzbuzz,而无需cmp。其他比较可以使用测试bl,bl和测试bh,bh来消除代码中的文字0(这使代码更小)。
\ $ \ endgroup \ $
–克里斯·杰斯特·杨(Chris Jester-Young)
15年7月13日在11:10
\ $ \ begingroup \ $
@ ChrisJester-Young所有出色的建议。谢谢!
\ $ \ endgroup \ $
–爱德华
15年7月13日在11:53
\ $ \ begingroup \ $
有趣的黑客为此使用了部分寄存器,特别是在避免写入一半寄存器之后避免读取更宽的寄存器的方式(除非仅重置其中一个计数器)。这将导致P6系列上的部分寄存器停顿。 (为什么GCC不使用部分寄存器?)。另一种选择是按3展开,并且仅使用一个减计数器,就像我在此FizzBuzz答案中使用的那样,我将字节存储在缓冲区中以进行打印。
\ $ \ endgroup \ $
– Peter Cordes
20 Dec 20 '20:27
\ $ \ begingroup \ $
test reg,reg优于cmp reg,0-它短1个字节,并且在所有情况下(AF除外)都完全相同地设置FLAGS。用CMP reg,0和OR reg,reg测试寄存器是否为零?
\ $ \ endgroup \ $
– Peter Cordes
20/12/21在23:29
#2 楼
使用本地标签您所有的标签都是全局标签。
由于所有这些标签都试图完成相同的任务,并且它们都可以一起使用,因此应将它们全部归为一个全局变量其余标签是本地标签。例如,您可以更改以下标签:
fizzbuzz_check:
.fizzbuzz_check:
这也是更好的做法。
不同的条件跳转
在每次检查Fizz,Buzz或FizzBuzz时,您都可以执行以下操作:
je print_fizzbuzz ; if so, proceed with printing
jmp fizz_check ; if not, try checking for fizz
print_fizzbuzz:
可以缩短为:
jne main_loop
print_fizzbuzz:
如果没有通过
jne
,执行将落入print_fizzbuzz
通用性
现在,您的代码仅支持Fizz,Buzz和Fizzbuzz。
但是,如果您想稍微改变一下怎么办?假设您想每第四个数字都说“嘶嘶响”?
要这样做,您需要添加大量代码。
有一种更简单的方法;使用
struc
。假设您创建了
struc
:struc message
.say: resb 10
.num: resb 1
endstruc
然后您可以执行以下操作轻松创建一堆消息:
messages:
db "FizzBuzz", 0, 0
db 15
db "Buzz",0,0,0,0,0,0
db 5
db "Fizz",0,0,0,0,0,0
db 3
db 0,0,0,0,0,0,0,0,0,0; so, when iterating, can know if the end has been reached
db 0
(多余的0是用于填充名称所给定的10个字节)
(请注意顺序:您希望最大到最小)
现在,您可以轻松地
在主代码中,遍历
messages
,如果计数器可以被num
字段中的值整除,则登录say
字段。现在,代码可以这样写:
xor cx, cx
main_loop:
inc cx
cmp cx, 100
jg .done
call search
jmp main_loop
.done:
.EXIT
search:
mov si, messages
.next:
mov ax, cx
mov bh, [si + message.num]; divisor
div bh
cmp ah, 0; was evenly divisible
je .print_message
add si, message_size
cmp byte [si], 0; the next item in `messages` is the terminator
jne .next
jmp .print_num
.print_message:
PutStr [si + message.say]
nwln
ret
.print_num:
PutInt cx
nwln
ret
注意:在没有
macros.s
的情况下进行测试很麻烦,因此如果有任何问题,请通知我评论
\ $ \ begingroup \ $
消息:\ .fizzbuzz:db“ FizzBuzz”,0 \乘以10-($-.fizzbuzz)db 0对于将字符串字段填充到十个字节长更好。
\ $ \ endgroup \ $
– ecm
19/12/10在17:55
\ $ \ begingroup \ $
如果使用.num == 0作为终止符,则每次迭代仅需加载一次(在第一次迭代之前加载第一次迭代的除数,即旋转并部分剥离循环以实现此目的)。或使用当前代码,div字节[si + message.num]避免浪费将其加载到BH中的指令。另外,您可以将.print_num放在.print_message之前,这样循环就可以陷入其中。主循环也可以使用更有效的do {cx ++; } while(cx <100);循环结构:为什么循环总是编译成“ do ... while”样式(尾跳)?
\ $ \ endgroup \ $
– Peter Cordes
20 Dec 20'20:39
\ $ \ begingroup \ $
搜索中的代码不必是一个函数,即使它很大,也可以只是循环体。这可能不太可读,但是如果要最大程度地提高可读性,请使用编译器。通常,使代码更通用是在asm中实现时要做的最后一件事:尽可能利用特殊情况。再一次,否则使用编译器,以便它可以即时利用固定常量(例如2和4为2的幂),除非代码真正需要处理除数和消息的运行时变量数组,否则它不使用div。
\ $ \ endgroup \ $
– Peter Cordes
20 Dec 20 '20:43
评论
真好! +1是努力编写这门低级语言。我可以建议发布结果集吗?@Phrancis:您要验证成功执行吗?
是的,基本上。尽管我毫不怀疑它执行了。
@Phrancis:我只是犹豫,因为这会占用很多(可能是不必要的)空间。截图的链接就足够了吗?
在哪里可以获取macros.s?