向后读取一个字符串并保存它(string reversal with a twist)

Reading a string backwards and saving it(string reversal with a twist)

我是新来的,但我会尽力遵守指南。 请注意,我才刚刚开始使用 MiPS 和 MARS,所以如果我做了一些愚蠢的事情,请告诉我如何修复它,以便我可以改进。

我目前正在为我的长途装配课程做一项学校作业,我必须反转字符串。 问题是我无法创建新字符串,而是必须修改现有字符串。

我想我为什么要尝试在字符串中找到第一个+n/last-n 个字符,而我只能从字符串中的最后一个字符向后移动到字符串的第一个字符。 我设法向后读取并打印字符串,但现在我被卡住了,真的可以在正确的方向上使用微调。

如果你 运行 这段代码然后字符串 "dlroW olleH" 将被打印并且程序将退出,到目前为止一切顺利(我希望)。 我的问题是:

如何将字符串以反向格式保存到原始标签"str"?

我开始编写一些代码,但尚未完成,请参阅 "reverse" 标签以了解与我的问题相关的部分。到目前为止,这是我的代码: 请注意,我不能使用任何库,可以这么说,我必须手动执行此操作。

查看下面的编辑

.data
str: .asciiz "Hello World"
str_msg1: .asciiz "String: "
str_msg2: .asciiz "String lenght: "
str_msg3: .asciiz "String reversed: "
str_nl: .asciiz "\n"
strLen: .space 8

.text

# Printing the original string
la $a0,str_msg1
li $v0,4
syscall
la $a0,str
li $v0,4
syscall
la $a0,str_nl
li $v0,4
syscall

#Get the lenght of the string
la $a0,str
getLen:
    lb $t0,0($a0)
    beqz $t0,printLen
    addi $t1,$t1,1
    addi $a0,$a0,1
    j getLen

#saves and prints the lenght of the string
printLen:
    sb $t1,strLen
    la $a0,str_msg2
    li $v0,4
    syscall
    lb $a0,strLen
    li $v0, 1
    syscall
    la $a0,str_nl
    li $v0,4
    syscall

这是有趣的部分:

reverse:
    addi $t0,$zero,0 #zeroing the t registers to clear them for the next part.
    addi $t1,$zero,0
    addi $t2,$zero,0

    la $a1,str             #contains the adress of the string
    lb $a2,strLen          #contains the lenght of the string 
    add $a1,$a1,$a2          #adds the lenght of the string to the adress meaning it now stores the last position of the string
    add $t0,$a2,$zero    #counter initiated to lengt of the string
    loop:
        subi $a1,$a1,1  #decrement the adress since we dont want the null terminator string
        beqz $t0,exit      #if the counter variable is zero we have gone over the entire range of the strings index,then exit
        subi $t0,$t0,1  #decrement the counter since if we are here then we have not reached the end yet
                          #<temporary print statement below is for debugging purposes>
        lb $a0,0($a1)   #loads the first byte from the adress stored at $a1 which will be the string in decending order
        li $v0,11         #print character syscall
        syscall
        j loop
# Exit the program
exit:
    li $v0,10
    syscall

感谢您花时间阅读我的问题。 干杯!

编辑

所以我可能已经找到了答案,我想我应该 post 它以便其他人可以阅读它,也许它可以得到改进。我在技术上避开了这项任务允许的内容,但最后原始字符串被更改为反向格式,所以我认为它应该没问题。我通过创建缓冲区来做到这一点 (strBuffer .space 255) 然后我将字符串反向复制到这个缓冲区,然后将其存储在原始字符串的地址。它似乎起作用了,我欣喜若狂!

我修改后的代码如下所示:

reverse:
    addi $t0,$zero,0 #zeroing all the t registers to clear them for the next part.
    addi $t1,$zero,0
    addi $t2,$zero,0
    addi $t3,$zero,0
    addi $t4,$zero,0
    la $a1,str      
    lb $a2,strLen       
    add $a1,$a1,$a2     
    add $t0,$a2,$zero   
    la $t3, strBuffer #here is the new buffer
    loop:
        subi $a1,$a1,1  
        beqz $t0,exit   
        subi $t0,$t0,1  
        lb $t4,0($a1)     # i load the string backwards byte by byte
        sb $t4,0($t3)     # i store it in the string buffer
        addi $t3,$t3,1     # i increment the memory adress of the buffer so that i can save the bytes one after the other
        j loop
exit:                      #I know my labels have to be changed but i will clean it later
la $a0,str_msg3           #print a leading message
li $v0,4
syscall
la $t8,strBuffer           #load the adress of the buffer and the string
la $t9, str
loop2:
    lb $t7,0($t8)          #load the first byte of the buffer
    beqz $t7,exit2         #check if its null 
    sb $t7,0($t9)          #store the byte in the strings adress at the first index
    addi $t8,$t8,1         #incrementing the adresses 
    addi $t9,$t9,1
    j loop2
exit2:                     #printing the result
la $a0,str
li $v0,4
syscall
li $v0,10
syscall

我已经在这里工作了 6 个多小时,所以请原谅我缺乏适当的风格和缩进。它似乎有效,从我对 MARS 数据段显示的了解来看,字符串被颠倒了。

干杯

反向原位算法建议:

Have two registers r1,r2 point to first/last character of string.
while (r1 < r2) {
    swap_chars_at_addresses(r1, r2);
    ++r1;
    --r2;
}

对您的原始代码的一些评论:

strLen: .space 8

您为 strLen 保留了 8 个字节,但随后在代码中将其用作 byte 变量,这会将您的代码限制为最多 127 个字符长的字符串(而字节可以是0..255 范围,lb 默认情况下会对值进行符号扩展,因此要达到完整的 255 限制,您必须将该字节视为无符号值)。

我强烈建议将其设置为:

strLen: .word 0

然后使用 sw/lw 将其视为 32b 有符号整数值,因为您有可用的字值,这在指针运算中更有意义。此外,MARS/SPIM 中有人会设法为您提供超过 231 个字符的输入字符串并使长度值溢出为负数的风险为零,因为内存不足在 MARS/SPIM 的虚拟 MIPS 机器上有这么大的字符串。虽然给你 128 个字符长的字符串并不难(然后你的旧代码会发疯)...

#Get the lenght of the string

拼写为 "length"(我知道 MARS/SPIM 没有拼写检查器,但您的网络浏览器可能有)。

la $a0,str
getLen:
    lb $t0,0($a0)
    beqz $t0,printLen
    addi $t1,$t1,1
    addi $a0,$a0,1
    j getLen

您没有初始化 t1,所以您很幸运 MARS/SPIM 在执行您的代码之前确实将寄存器清零并且没有在 syscall 中修改它们来电。

但这仍然是一种脆弱的编程方式,而是初始化所有相关寄存器并使相关指令尽可能紧凑(彼此靠近)(即在 [=23= 之前初始化,而不是在开始之后立即初始化)打印提示前的代码)。

此外,如果您看一下该代码,就会发现 .. += 1; 两次。这应该感觉像是不必要的冗余。事实上你可以避免这种情况:

la $a0,str
move $a1,$a0 # have str address also in a1
getLen:
    lb   $t0,0($a0)
    beqz $t0,getLenFinish
    addi $a0,$a0,1
    j getLen            # loop is one instruction shorter = may be faster
getLenFinish:
    subu $t0, $a0, $a1  # length = address of 0-terminator - str address
    sw   $t0,strLen     # store the calculated length into memory

现在根据我的评论设置那些 "r1, r2" 就这么简单:

    # set t1 and t2 to point to first and last character of string
    la    $t1,str
    lw    $t2,strLen
    addu  $t2, $t1, $t2   #t2 = address of zero terminator
    subi  $t2, $t2, 1     #t2 = adr of last char (or out of bounds)
    # for empty string t2 is out of bounds now and shouldn't be dereferenced
    # And my algorithm was designed as "while (r1 < r2)" = false => no problem

这就是我要停止的地方,因为只有这么多评论很有趣...

P.S。 顺便说一句,我喜欢你如何在不反转的情况下设法打印出反转的字符串,这表明你可能对正在发生的事情以及该计算机盒的工作方式有扎实的基本了解。许多其他 MIPS 问题感觉作者甚至不知道 "string" 是存储在内存中的一系列字节值,而且您实际上可以向后读取它。

哇,感谢大家的大力帮助!在晚上睡得很好之后,我检查了我的代码,天哪,它看起来很乱。在阅读了您的回复后,我重写了整个内容并且它有效:D(请参阅底部的代码)

非常感谢@Ped7g 和@PeterCordes 发表了如此多的评论,告诉我我可以改进什么以及在使用 MiPS 时我应该考虑的一些事情。谢谢,今天学到东西了

.data 
str: .asciiz "ThE qUiCk BrOwN fOx JuMpS oVeR tHe LaZy DoG"
str_msg1: .asciiz "Original string: "
str_msg2: .asciiz "Reversed string: "
str_nl: .asciiz "\n"
str_len: .word 0

.text
main:
#print original string
    la $a0,str_msg1     #leading text
    li $v0,4
    syscall
    la $a0,str              #original string
    li $v0,4
    syscall
    la $a0,str_nl           #new Line
    li $v0, 4
    syscall

#get lenght
    add $t0,$zero,$zero     #initialize registers when needed
    add $a0,$zero,$zero
    add $a1,$zero,$zero
    la $a0,str            #loads the adress of the string into two registers
    la $a1,str      
    getLen:
        lb $t0,0($a0)       #load first byte
        beqz $t0,saveLen  #check if byte is null and if so, goto saveLen
        addi $a0,$a0,1  #if not then increment the adress and keep going
        j getLen          #jump back to start of this loop
    saveLen:
        subu $t0,$a0,$a1  #len = adress of null terminator - str adress
        sw $t0,str_len

#reverse the string
    add $t0,$zero,$zero     #will hold the adress of the beginning of str
    add $t1,$zero,$zero     #will hold the adress of the end of str
    add $t2,$zero,$zero     #swap 1 
    add $t3,$zero,$zero     #swap 2 
    revString:
        #find the index of the last character before the end of the string
        la $t0,str             #loads the adress of the start of the string
        lw $t1,str_len         #loads the lenght of the string
        addu $t1,$t0,$t1      #now t1 is pointing to the null terminator
        subi $t1,$t1,1      #now t1 is pointing to the last character
        loop:
            lb $t2,0($t0)       #load the first character
            lb $t3,0($t1)       #load the last character
            ble $t1,$t0,printRev    #check to see when we reach the middle of the string
            sb $t3,0($t0)       #store the last letter at the beginning of the string
            sb $t2,0($t1)       #store the first letter at the end of the string
            addi $t0,$t0      #then increment/decrement the adress registers 
            subi $t1,$t1,1    #and loop until we reach the middle
            j loop

#print the reversed version of the text
    printRev:
    add $a0,$zero,$zero    #initialize the a0 registry
    la $a0,str_msg2      #leading text
    li $v0,4
    syscall
    la $a0,str          #reversed string
    li $v0,4
    syscall
    li $v0,10           #exit prorgram
    syscall

P.S. BTW, I like how you managed to print out the reversed string without reversing it, that shows you have probably solid basic grasp of what is going on and how that computer box works ...

谢谢,伙计,读起来真好 :) 我现在对自己感觉好多了。尽管如此,在像这样的网站上提问后,我总是想起我还有多少不知道。

幸运的是,有些人像您一样愿意花时间分享他们的经验和建议。好人!