【问题标题】:Iterating over a Linked List in MIPS迭代 MIPS 中的链表
【发布时间】:2021-04-29 14:34:16
【问题描述】:

这是我第一次使用程序集,我正在尝试实现一个链表。 每个节点是 2 个字 - 第一个是节点的值,第二个是列表中下一个节点的地址。对于最后一个节点,next 为零。 列表的基数是包含第一个节点地址的单词,如果列表为空,则为 0。

我正在尝试实现函数“添加项目”,其中第一个参数 ($a0) 是列表基址的地址,$a1 是存储我要添加到我的值的地址列表。 为此,我尝试遍历列表并找到最后一项,因此我将其“下一个”值设置为我创建的新节点。

我觉得这很丑陋(我同时使用“循环”和“增加”)并且我错过了一种更简单的方法来遍历列表,但我有点困惑如何做到这一点正确使用一个循环,因为我想在列表结束前停止 1 步 有什么更好的方法来实现这一目标?

谢谢!!

AddItem:
    # first I make the new node:
    addi $sp,$sp,-8         # we make room in stack for the new item
    lw $t0,0($a1)           # load new item's value from its address
    sw $t0,0($sp)           # set new node's value
    sw $zero,4($sp)         # set new node's next to 0 because it's now the last item
    # now I want to find where to put it:
    lw $t2,0($a0)           # $t2 contains the address of the first node in the list
    beq $t2,$zero,AddFirstItem  # in case the list is empty and we add the first item
    # if the list is not empty we need to find the last item:
    add $t0,$zero,$t2       # initialize $t0 to point to first node
    loop:
    lw $t1,4($t0)           # $t1 is the address of next item
    bne $t1,$zero,increase
    j addItem           # when we reach here, $t0 is the last item of the list
    increase:           # we iterate on the items in the list using both "loop" and "increase"
    add $t0,$t1,$zero       # $t0 which is the current item is now updates to current item's next
    j loop
    
    addItem:
    sw $sp,4($t0)           # set current item ($t0) next to be the node we created 
    j EndAddItem
    
    AddFirstItem:
    sw $sp,0($a0)       # setting the base of the list to the node we created
    
    EndAddItem:
    jr $ra

【问题讨论】:

    标签: assembly mips


    【解决方案1】:

    你的遍历列表的代码没问题,看起来有点像这样:

    ...
    while ( node != null ) {
        prev = node;
        node = node -> next;
    }
    // node is null and prev is the last non-null pointer
    

    就改进而言,这里:

        bne $t1,$zero,increase
        j addItem           # when we reach here, $t0 is the last item of the list
    increase:
    

    这个条件分支围绕一个无条件分支分支。这是不必要的,因为我们可以反转条件并交换分支目标:

        beq $t1,$zero,addItem
        # j addItem           # when we reach here, $t0 is the last item of the list
    #increase:
    

    您将能够消除j addItem,并且标签increase 也将不再需要。


    为了进一步改进,您的主列表对象可以保留一个指向列表末尾的指针。虽然随着节点的添加和从列表中删除,这将需要更多的维护,但它将消除查找列表末尾的搜索循环。


    就您的内存分配而言,您正在为保持函数实例化的数据结构节点分配堆栈上的节点。这可能在短期内有效,但从长远来看,在运行时堆栈上分配持久数据结构非常容易出错。一个函数应该返回给它的调用者,堆栈的顶部(即堆栈指针的值)与它被调用时的位置相同。如果此函数的任何调用者以正常方式使用堆栈,他们将无法找到基于堆栈的变量/存储。


    MARS 和 QTSPIM 没有正确的 mallocfree 函数,但它们应该用于持久内存分配。这些模拟器有sbrk 可以替代malloc 但没有对应的free 操作。 (我们可以说正确的mallocfree 留给读者作为练习;)

    您可以更改堆栈的头部,但必须在返回时将其恢复到原来的位置。通过这种方式,堆栈可用于生命周期与函数调用的持续时间相匹配的任何存储需求。

    例如,假设main 调用AA 调用BB 调用C。所以,当main 使用jal A 时,它会在$ra 中传递A 一个(从C 中隐藏的)参数,它告诉A 如何返回到main。然而,在A 完成之前,它调用B,使用jal Bjal 将重新调整$ra 寄存器的用途,现在作为B 的参数返回到A。如果没有缓解,A$ra 参数(返回到main)将被清除。因此,A 使用jal 调用B 之前,它保存了原来的$ra 值。它最后需要这个值,所以它可以返回到main。因此,在调用A 期间,A 将其$ra 值存储在堆栈中。这在逻辑上释放了$ra 寄存器,以便重新用于调用BB 也有类似情况。如果C 是我们所说的叶函数(不进行任何函数调用),那么C 将简单地留下$ra 并在最后使用它。 C返回BB从它分配的栈内存中取出它的返回地址,释放它分配的栈内存,然后返回到AA 还获取它存储在堆栈分配内存中的返回地址,它可以找到它,因为堆栈指针具有与存储返回地址时相同的值:jal B 操作,就A 而言,没有改变栈顶,在调用B 之后它在同一个地方——尽管B 也分配(并释放)了一些内存。

    总而言之,在许多情况下,函数需要一些本地存储,其生命周期与函数调用的持续时间完全匹配。堆栈是满足这些需求的廉价内存分配机制。

    【讨论】:

    • 谢谢!关于内存分配 - 这是一个我不完全理解的好点。 “长期”存储数据的正确位置在哪里?另外,如果我不应该更改堆栈的头部,它有什么用?是否只用于临时操作?
    • 我附加了一些额外的材料来回答这些问题。
    猜你喜欢
    • 1970-01-01
    • 2013-11-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-08-01
    • 2013-10-29
    相关资源
    最近更新 更多