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结尾。与POSIXwrite(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