装配 MIPS:嵌套循环 [英] Assembly MIPS: Nested loops

查看:97
本文介绍了装配 MIPS:嵌套循环的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

当我尝试编写一些代码来打印五行星号乘以 4 时,确实有点棘手.

It surely got a bit tricky when I tried to write some code that would print five lines of the asterisk symbol times 4 in each one.

****
****
****
****

所以我认为嵌套循环可以挽救这一天.男孩,我错了.

So I thought a nested loop could save the day. Boy I was wrong.

所以我为星号做了一个内循环,为空格做了一个外循环,如下所示:

So I made an inner loop for the asterisks and an outer loop for the spaces as below:

.text
.globl main
main:
add $t0, $zero, $zero   #i counter for the inner loop
add $t2, $zero, $zero   #j counter for the outer loop

outerloop:

    innerloop:

        slti    $t1, $t0, 4     #while (i<4)
        beq     $t1, $zero, innerexit

        li      $v0, 11         #printf("*");
        la      $a0, '*'
        syscall

        addiu   $t0, $t0, 1     #i++

    j innerloop
    innerexit:

slti    $t3, $t2, 5     #while (j<5)
beq     $t3, $zero, outerexit

li      $v0, 11         #printf("\n");
la      $a0, '\n'
syscall

addiu   $t2, $t2, 1     #j++

j outerloop
outerexit:

li  $v0, 10
syscall

但输出只给了我一行:

****

外循环怎么了?

推荐答案

最简单的方法是使用 write-string 系统调用 N 次,使用非嵌套循环.(可以说,制作一个包含所有行的长字符串甚至更简单",但可维护性较差,并且不利于具有大 N 的程序的大小).

The simplest way would be to use the write-string system call N times, with a non-nested loop. (Well arguably making one long string containing all the lines would be even "simpler", but less maintainable, and bad for the size of your program with large N).

注意递减计数器的使用,向零递减计数,这样我们就可以bne针对$zero.这是 asm 的惯用语,所以是循环底部的条件分支.特别是对于您知道行程计数保证至少为 1 的任何循环.(如果不是这种情况,您通常会在需要时使用循环外的分支来跳过它.)

Note the use of down-counters, counting down towards zero so we can bne against $zero. This is idiomatic for asm, and so is putting the conditional branch at the bottom of the loop. Especially for any loop where you know the trip-count is guaranteed to be at least 1. (When that's not the case, you'd normally use a branch outside the loop to skip it if needed.)

## Tested, works in MARS 4.5
.data
line: .asciiz "****\n"

.text
.globl main
main:
   li  $t0, 4     # row counter
   li  $v0, 4     # print string call number
   la  $a0, line

 .printloop:
   syscall           # print_string has no return value, doesn't modify v0
   addiu  $t0, $t0, -1
   bnez   $t0,  .printloop           # shorthand for BNE $t0, $zero, .printloop

   li  $v0, 10       # exit
   syscall


您可以在临时缓冲区中生成字符串,并在打印循环之前的单独循环中对寄存器进行计数.因此,您仍然可以支持运行时变量的行和列计数,使用两个顺序循环而不是嵌套.


You could generate the string in a temporary buffer, with a count from a register in a separate loop before the print loop. So you can still support runtime-variable row and column counts, with two sequential loops instead of nested.

使用 4 对齐的缓冲区,我们可以一次存储 4 个字符的整个单词,这样循环就不必运行尽可能多的迭代.(li reg, 0x2a2a2a2a 需要 2 条指令,但 li reg, 0x2a2a 只需要一条指令,所以用 sh 加上 2 会使代码更小).

With a buffer aligned by 4, we can store a whole word of 4 characters at once so that loop doesn't have to run as many iterations. (li reg, 0x2a2a2a2a takes 2 instructions but li reg, 0x2a2a only takes one, so going by 2 with sh would make the code smaller).

.text
.globl main
main:
.eqv    WIDTH, 5
.eqv    ROWS, 4

   addiu  $sp, $sp, -32         # reserve some stack space.    (WIDTH&-4) + 8   would be plenty, but MARS doesn't do constant expressions.
   move   $t0, $sp
   
   addiu  $t1, $sp, WIDTH       # pointer to end of buf = buf + line length., could be a register
   li     $t2, 0x2a2a2a2a           # MARS doesn't allow '****' or even '*' << 8 | '*'
 .makerow:                  # do{
   sw     $t2, ($t0)          # store 4 characters
   addiu  $t0, $t0, 4         # p+=4
   sltu   $t7, $t0, $t1
   bnez   $t7, .makerow     # }while(p < endp);
# overshoot is fine; we reserved enough space to do whole word stores
   li     $t2, '\n'
   sb     $t2, ($t1)
   sb     $zero, 1($t1)     # terminating 0 after newline.  Unfortunately an sh halfword store to do both at once might be unaligned

   move   $a0, $sp
   li     $t0, ROWS
   li     $v0, 4             # print string call number

 .printloop:
   syscall                   # print_string has no return value, doesn't modify v0
   addiu  $t0, $t0, -1
   bnez   $t0,  .printloop           # shorthand for BNE $t0, $zero, .printloop.      # }while(--t != 0)


## If you were going to return instead of exit, you'd restore SP:
#  addiu $sp, $sp, 32

   li  $v0, 10       # exit
   syscall

正如预期的那样,这会在每一行打印 5 个星号.

As expected, this prints 5 asterisks on every row.

*****
*****
*****
*****


通常(在实际系统中)系统调用比普通指令要昂贵得多,因此准备具有多个换行符的单个大缓冲区实际上是有意义的.(系统调用的开销使写入 1 字节和 5 字节甚至 20 字节之间的差异相形见绌,因此即使调用 print_string 而不是 print_char 是一种隐藏在系统调用中的工作,这是合理的.)


Generally (in real systems) a system-call is much more expensive than normal instructions, so preparing a single large buffer with multiple newlines would actually make sense. (The overhead of a system call dwarfs the difference between writing 1 vs. 5 or even 20 bytes, so even though calling print_string instead of print_char is kind of hiding work inside the system call, it's justified.)

在这种情况下,您可能需要嵌套循环,但是使用 sb/addiu $reg, $reg, 1 指针增量而不是 syscall.最后只进行一次系统调用.

In that case you probably would want nested loops, but with sb / addiu $reg, $reg, 1 pointer-increment instead of syscall. Only make one system call at the very end.

或者一次存储所有 * 字符 4 的循环(对于 ROWS * COLS/4 舍入迭代),然后另一个循环插入 \n' 它们所属的换行符.这让您可以使用更少的指令将所有数据放入内存,而不是一次按 1 个字节的顺序执行所有操作.(对于非常大的 row*col 计数,您可能会将缓冲区大小限制为 4 或 8 kiB 或其他大小,因此当内核的系统调用处理程序读取数据以将其复制到需要的任何位置时,您的数据仍在缓存中.)

Or a loop to store all the * characters 4 at a time (for ROWS * COLS / 4 rounded up iterations), then another loop that inserts the \n' newlines where they belong. This lets you get all the data into memory with fewer instructions than doing everything in order 1 byte at a time. (For very large row*col counts, you would probably limit your buffer size to 4 or 8 kiB or something, so your data is still in cache when the kernel's system call handler reads it to copy it to wherever it needs to be.)

顺便说一句,在 C 语言中,print char 系统调用更像是 putchar('*'),而不是 printf("*").请注意,您是按值传递一个字符,而不是指向以 0 结尾的字符串的指针.

BTW, in C terms, the print char system call is more like putchar('*'), not printf("*"). Note that you're passing it a character by value, not a pointer to a 0-terminated string.

这篇关于装配 MIPS:嵌套循环的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆