如何通过替换地址中的内存而不是在 MIPS 汇编中创建新内存来正确反转字符串

How to properly reverse a string by replacing the memory in the adress, not creating new memory in MIPS Assembly

我在我的计算机体系结构 class 中有一个任务,我们应该在其中完成一组任务。最后一项任务是 创建一个子例程,该子例程接受一个字符串并将其反转 我们不允许使用原始字符串来创建"new memory string"。相反,我们应该逐渐替换原始字符串的内容

我的第一个想法是将最左边和最右边的字符加载到临时寄存器中,然后使用它们来 "swap" 它们的位置。完成后,地址应该增加和减少(有两个地址,一个指向字符串的开头,一个指向结尾)。然后这将循环直到两个地址都指向同一点,然后循环将结束。

这是 reverse_string 子例程的代码。 $a0 是以 NULL 结尾的字符串的地址。我不确定我脑子里想的算法是否正确地翻译成 MIPS,我对这种语言很陌生。

reverse_string:

    #### Write your solution here ####
    beqz    $a0, rs_exit        # Check if string is NULL, exit if it is
    li  $t0, 0
    li  $t1, 0
    add $t0, $t0, $a0       # Save leftmost adress of string
    add $t1, $a0, $v0       # Save rightmost adress of string

    rs_loop:
    beq $t0, $t1, rs_exit
    lbu $t2, 0($t0)     # Save leftmost character in temporary register
    lbu $t3, 0($t1)     # Save rightmost character in temporary register
    sb  $a0, 0($t2)     # Replace rightmost character with leftmost character
    sb  $a0, 0($t3)     # Replace leftmost character with rightmost character
    addi    $t0, $t0, 1     # Increment leftmost adress by 1
    subi    $t1, $t1, 1     # Decrement rightmost by 1
    j   rs_loop

    rs_exit:
    jr  $ra

main中执行reverse_string的代码如下:

    ##
    ### reverse_string
    ##

    li  $v0, 4
    la  $a0, STR_reverse_string
    syscall

    la  $a0, STR_str
    la  $a1, reverse_string
    jal reverse_string

    la  $a0, STR_str
    jal print_test_string

因此,如前所述。预期的结果是程序应该打印出反转的字符串。目前,我在以下行遇到错误:

sb  $a0, 0($t2)     # Replace rightmost character with leftmost character

错误:

Runtime exception at 0x004000c4: address out of range 0x0000004a

我已经试了几个小时了。有几个人已经成功地获得了类似问题的帮助,但是他们有点不同(来自用户的输入并且他们创建了新字符串而不是替换原始字符串的内容)

感谢您的帮助!谢谢。

My first thought was to ... this is going to loop until both of the addresses point to the same point...

直到"end"指针等于或小于"start"指针。对于像 "abcd" 这样的偶数长度,指向 "b" 和 "c" 的指针是有效的,但是在交换和递增+递减之后它们仍然不相等,但是你应该结束循环。总之,除了这个细节,你的想法还是不错的。

Runtime exception at 0x004000c4: address out of range 0x0000004a

这意味着 sb(存储字节)指令确实尝试在内存地址 0x0000004a 处写入,该地址不可访问(那里没有内存,或者您的进程没有足够的权限在那里写入).这意味着 0($t2) 确实评估了该地址,这意味着 t2 中的值等于 0x4a。当单步执行指令时,您应该能够在调试器中看到这一点。

从那里你必须回溯整个操作,它是如何变成这个值的,为什么。


    la  $a0, STR_str
    la  $a1, reverse_string
    jal reverse_string

为什么设置a1?任务中的合同说字符串地址是在 a0 中传递的,没有别的。但无论如何,这不是你的代码,只是好奇...所以让我们开始你的代码吧。

beqz    $a0, rs_exit        # Check if string is NULL, exit if it is
li  $t0, 0
li  $t1, 0
add $t0, $t0, $a0       # Save leftmost adress of string
add $t1, $a0, $v0       # Save rightmost adress of string

null 测试没问题,您也可以使用 addi$zero = [=22=] 作为零值,即:

    beqz    $a0, rs_exit  # Check if string is NULL, exit if it is
    add $t0, $a0, $zero   # t0 = left pointer (start of string)
    add $t1, $a0, $v0     # t1 = start of string plus unknown value in v0

正如您在第二条评论中看到的那样,一开始就有一个错误。

这意味着您根本没有调试代码,或者输入非常有限。

如果例程的唯一输入是字符串地址,则必须逐个字符地查找存储终止零的位置,并使用它来计算 "end" 指针。 (例如,您可以将 a0 复制到 a1,从 a1 加载字节,检查零,递增 a1 并再次获取,...直到找到终止零...第一个零前面的地址 (-1) 是您的 "end" 指针)

让我们假装你有正确的指针......然后是另一部分:

lbu $t2, 0($t0)     # Save leftmost character in temporary register
lbu $t3, 0($t1)     # Save rightmost character in temporary register
sb  $a0, 0($t2)     # Replace rightmost character with leftmost character
sb  $a0, 0($t3)     # Replace leftmost character with rightmost character

前两个是正确的(指针正确)。但是另外两个是完全错误的。 "sb" 存储字节有参数 "value, memory address",所以你试图将字符串地址的底部字节存储到由字符表示的内存地址... 0x4a 是 ASCII 编码字符 'J'],正如错误消息指出的那样,它不是有效的内存地址。

你可能觉得自己还挺幸运的,因为在用汇编编程的时候,有时候类似的bug居然恰好在寄存器中有错误的值,是可以访问的,一些本不应该被覆盖的内存被覆盖了,但没有崩溃或任何问题的迹象。然后很久以后,一些完全不同的代码部分可能会访问该内存,期望其他东西存储在那里,它会产生一些错误。这些 "memory overwrite" 错误极难破译和修复,任何老派 assembly/C/C++ 程序员都可以告诉你。

所以你的代码很弱尝试实现你描述的想法。

尝试使用调试器 运行 通过它(如果您使用的是 SPIM/MARS 模拟器,它们都有内置调试器,不是最先进的调试器,但可用于这些微小的教程任务), 并尝试完全理解正在发生的事情,为什么这些指令不代表你的想法,以及它们在现实中的实际作用。

你必须学习这项技能,如果你想在汇编中编码,汇编不允许错误或一些模糊的解释,或者只是通过 "trying" 东西,随机更改源代码来获得工作代码。始终弄清楚代码实际上做了什么,以及它与您想要的有何不同。然后修复它。

一般来说,显示 "no debugging" 的组装问题很快就会被否决,因为调试是一个耗时的过程,将它外包给 SO 人群是不礼貌的......但你可以从我这里得到加分,因为你清楚地说明了你的想法,并且总体上提供了几乎完整的可重现示例(您确实忘记了显示字符串定义...并且在汇编中,定义数据的方式通常比代码更重要,因此您可能有某种错误也在那里,比如不向字符串添加零终止符等...)。

也永远不要试图通过它的名字来猜测指令是如何工作的。始终正确地研究参考手册,并确保您理解其中关于说明的所有内容。

您可以通过猜测和尝试高级语言中的随机事物来获得一些较弱的结果,但这只是在汇编中浪费时间,即使是大约 20 行的简短例程也已经允许数百万种变化(起初有点有意义视线),只有几百个是正确的解决方案。

现在再试一次,专注于始终控制自己。如果您不确定某事,如何理解它,请多读几遍,或者构建简短的代码来练习您不确定的部分,并在调试器中检查 CPU 的作用...最终询问所以解释一下你的期望,以及调试器中让你惊讶的地方。