String 如何在MIPS中反转多行字符串的行顺序?

String 如何在MIPS中反转多行字符串的行顺序?,string,assembly,mips,string-concatenation,low-level,String,Assembly,Mips,String Concatenation,Low Level,我有一条多行信息,形式如下: .data msg: .ascii "aYZ B" .byte 10 .ascii "234" .byte 10 .ascii "b cd A" .byte 10 我需要颠倒这些行的打印顺序,以便: aYZ B--------------B cd A 234----变成--234 b cd A-------------------aYZ b 到目前为止,我的总体

我有一条多行信息,形式如下:

.data
    msg:
        .ascii "aYZ B"
        .byte 10
        .ascii "234"
        .byte 10
        .ascii "b cd A"
        .byte 10
我需要颠倒这些行的打印顺序,以便:

aYZ B--------------B cd A

234----变成--234

b cd A-------------------aYZ b

到目前为止,我的总体想法是将第一个字符的地址推送到堆栈上,然后遍历消息(msg的基址+偏移量计数器),并将“\n”字符(.byte 10)之后的每个字符的地址立即推送到堆栈上(索引“\n”字符+1)。

然后,我将能够以相反的顺序弹出堆栈中每行的第一个字母

我正在努力解决的是如何在循环时修改原始msg。我应该按照相反的顺序构建一个新的msg吗?如果是,怎么做?我猜我会使用字符串连接来实现这一点?

最后,如何打印该消息?(我可以使用syscall4,但我需要将整个消息存储在一个标签中)

编辑:

因此,我设法将一个解决方案组合在一起,并几乎正确地工作。它有一个小错误:消息的最后一行不会打印在它自己的行上,它只是在倒数第二行之后立即打印

如果有人知道如何解决这个小问题,我很想知道如何解决

.data
    key: .byte 1
    msg:
        .ascii "ayZ B"
        .byte 10
        .ascii "234"
        .byte 10
        .ascii "b cD a"
        .byte 10 
.text
main:
    jal reverseLinesAndPrint
    # Exit Program
    li $v0, 10
    syscall

reverseLinesAndPrint:           # $s3 contains address of last char, $s0 contains offsets for both front and back, but must be reset before using for back 
    li $s0, 0                   # RESET value of msg position offset index to iterate from beginning again
    lineLoop:
    add  $s1, $t0, $s0          # Set $s1 equal to the base address of msg ($t0) + the position offset index ($s0)
    lb   $s2, ($s1)             # Deref. and move the current char into s2 for checking
    bne $s2, $zero, notLastChar # If the current char is not the last char in the msg, keep looping
        subi $s3, $s1, 1        # Subtract 1 from the ADDRESS of $s1 to get the last char ('\n') before the NULL Terminator and store it in $s3
        j lastCharIndexFound    # Exit the loop by jumping past it
    notLastChar:
    addi $s0, $s0, 1            # Increment the position offset index
    j lineLoop

    lastCharIndexFound:         # We now have the address of the last valid char in message (always '\n') stored in $s3
    li $s0, 0                   # RESET value of msg position offset index to iterate from ending this time
    reverseLineLoop:
    sub $s1, $s3, $s0           # This time, we are going to subtract from the starting address so we can iterate backwards over msg
    bne $t0, $s1, notFirstChar  # If we iterate all the way to the very first char in msg, exit the loop
        li $v0, 4               # Since the first char doesn't have a '\n' char, we have to manually print
        move $a0, $s1
        syscall
        j exit                  
    notFirstChar:
    lb $s2, ($s1)               # Deref. and move the current char into s2 for checking
    bne $s2, 10, notNLChar      # If we find a '\n' char, we need to do some logic
        li $v0, 4               # First we need to call a syscall to print string (it will stop on the previous '\n' which is now NULL)
        move $a0, $s1
        syscall
        sb $zero, ($s1)         # Second, we need to replace that current '\n' char with NULL
    notNLChar:                  # If the current char is not '\n', keep looping
    addi $s0, $s0, 1            # Increment the offset
    j reverseLineLoop           # Jump to next iteration

exit:
    jr $ra  # Jump back to main

我想你是在一行之前使用换行符将它与之前打印的行分开。这是一个聪明的想法,比只打印一行(没有前面的换行符)更有效。否则,您必须进行单独的打印单字符系统调用,如使用
$v0=11
/
$a0='\n'
()

这意味着您的输出类似于
“\nline3”
,然后是
“\nline2”
,等等。将光标保留在每行的末尾

但是您需要对最后一行(输入字符串的第一行)进行特殊处理,因为它前面没有
\n
。您已经对它进行了特殊的大小写,因此只需在它前面手动打印一个
\n
,作为前一行的结尾,并使用print char syscall


另一种方法可能是将
0
存储在换行符后的字符上,位置为
1($s1)
,因此当您稍后到达此行的开头时,可以将其打印为
“line2\n”
,并在末尾添加一个换行符。(以下是我的版本。)

这种特殊情况将成为输入的最后一行(输出的第一行),但如果您有一个以0结尾的C样式隐式长度字符串,那么在其换行后存储一个
0
字节实际上是可以的。那里已经有一个了,所以你可以在进入外部循环时跳过它,如果不这样做更方便的话,也可以不跳过


不修改数据:
写入(1,行,长度)
$v0=15
)采用指针+长度,因此不需要字符串以0结尾。与POSIX
write(intfd,char*buf,size\u t len)完全相同
。文件描述符
$a0=1
在MARS4.3及更高版本中是标准的

当你找到一条换行符时,你可以记录这个位置并继续循环。当您找到另一个时,您可以执行
subu$a2、$t1、$t0
($a2=end-start)来获取长度,并将
$a1
设置为指向换行符后的字符

因此,您可以打印您选择的数据块,而不必损坏输入数据,使其可用于只读输入,也不必制作副本以供以后需要时销毁


其他资料/代码审查

你的代码很奇怪;调用
reverseLinesAndPrint
时,没有在main的寄存器中放置指针和长度或结束指针,那么为什么要将其作为单独的函数呢?它不能重复使用

通常的做法是在ASCII数据块的末尾添加另一个标签,这样就可以将该地址放入寄存器,而无需扫描字符串来查找长度。(特别是因为在字符串末尾没有明确的
0
字节来终止它。有一个原因是您没有将任何其他数据放在后面,并且当您使用将数据段的起始地址放在地址0的内存模型时,MARS在数据和代码之间留有一个间隙。)

你甚至从不使用
la$reg,msg
。您似乎将其地址硬编码为
0
?您可以读取
$t0
,而无需先初始化它。火星开始时所有寄存器都归零。(因此,可能会漏掉像这样的bug,因为这是一个使用您选择的内存布局的有效地址。)

在正常的MIPS调用约定中,
$s
寄存器被保留(“保存”),也称为非易失性寄存器。但是您的函数将它们用作临时对象,而不保存/恢复它们。使用
$t
寄存器(以及$a0..3和$v0..1)将是正常的

循环效率很低:可以将条件分支放在底部,就像
do{}while()
一样。编写循环的方式非常复杂,每个循环迭代包含2个执行的分支(包括无条件循环分支)。或3用于搜索循环,您需要在其中检查
\n
p==end

// your loops are over-complicated like this:
do {
 loop body;
 if (p == end) {  // conditional branch over the loop epilogue
   stuff;         // put this after the loop instead of jumping over it inside the loop
   goto out;
 }
 counter increment;
} while(1);
out:
另外:在某处写一段评论,说明每个寄存器的用途。对于某些情况,它可能位于初始化寄存器的指令上

总的来说,你的评论相当不错,主要是在更高的层次上描述正在发生的事情,而不是像“将1添加到$s0”这样的东西,你已经可以从实际的说明中看到


我是这样做的 我使用了在打印后覆盖行的第一个字符的想法。这是新行后面的字节。所以当我们打印行时,它们就像
line2\n
而不是
\nline2

我还在
msg
的末尾添加了一个标签,而不是使用strlen循环。如果你要在横线上向前迭代
.data
 msg:
    .ascii "ayZ B\n234\nb cD a\n"
 endmsg:         # 0 terminated *and* we have a label at the end for convenience.
    .byte 0

.text
main:
    la   $a0, endmsg
    la   $a1, msg
    jal reverseLinesAndPrint   # revprint(end, start)

    li $v0, 10
    syscall           # exit()

reverseLinesAndPrint:
# $a0 = end of string.  We assume it's pointing at a '\0' that follows a newline
# $a1 = start of string
# $t2 = tmp char
# we also assume the string isn't empty, i.e. that start - end >= 2 on function entry.

# the first char the inner loop looks at is -2(end)

 #move  $t0, $a0          # instead we can leave our args in a0, a1 because syscall/v0=4 doesn't modify them

 lines:                       
   findNL_loop:                    # do {  // inner loop
     addiu  $a0, $a0, -1             # --p
     beq    $a1, $a0, foundStart     # if(p==start) break
     lbu    $t2, -1($a0)                # find a char that follows a newline
     bne    $t2, '\n', findNL_loop   # }while(p[-1] != '\n');
   # $a0 points to the start of a 0-terminated string that ends with a newline.
   foundStart:
    li     $v0, 4
    syscall                        # fputs(p /*$a0*/, stdout)

    sb     $zero, ($a0)            # 0-terminate the previous line, after printing
    bne    $a0, $a1, lines         # } while(!very start of the whole string)

    jr $ra