Assembly 在MIPS中对链表进行迭代

Assembly 在MIPS中对链表进行迭代,assembly,mips,Assembly,Mips,这是我第一次使用汇编,我正在尝试实现一个链表。 每个节点有2个字-第一个是节点的值,第二个是列表中下一个节点的地址。对于最后一个节点,next为零。 列表的底部是一个包含第一个节点地址的字,如果列表为空,则为0 我正在尝试实现函数“additem”,其中第一个参数($a0)是列表底部的地址,$a1是存储我要添加到列表中的值的地址。 为了做到这一点,我尝试遍历列表并找到最后一项,因此我将其“next”值设置为我创建的新节点 我觉得这很难看(我同时使用了“循环”和“增加”),我错过了一种更简单的方法

这是我第一次使用汇编,我正在尝试实现一个链表。 每个节点有2个字-第一个是节点的值,第二个是列表中下一个节点的地址。对于最后一个节点,next为零。 列表的底部是一个包含第一个节点地址的字,如果列表为空,则为0

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

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

谢谢

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

您遍历列表的代码是正确的,看起来有点像这样:

...
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附加项
,并且不再需要标签
增加


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


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


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

您可以更改堆栈的头,但必须将其还原到返回时的位置。这样,堆栈将用于生命周期与函数调用持续时间匹配的任何存储需求

例如,假设
main
调用
A
A
调用
B
B
调用
C
。因此,当
main
使用
jala
时,它传递
$ra
中的
A
A(对C隐藏)参数,它告诉
A
如何返回到
main
。但是,在
A
完成之前,它使用
jal B
调用
B
jal
将重新利用
$ra
寄存器,现在作为
B
返回到
A
的参数,而不作任何缓解,
A
$ra
参数(返回
main
)被清除。因此,在
A
使用
jal
调用
B
之前,它保存原始的
$ra
值。它需要在末尾使用此值,以便可以返回到
main
。因此,在调用
A
期间,
A
将其
$ra
值存储在堆栈上。这在逻辑上释放了
$ra
寄存器以重新调整其用途以调用
B
。类似的情况也发生在
B
中。如果
C
是我们所称的叶函数(不进行任何函数调用),然后
C
只需将
$ra
单独留下,并在最后使用它。
C
返回到
B
,并且
B
从它分配的堆栈内存中获取它的返回地址,释放它分配的堆栈内存,然后返回到
A
A
还获取它存储在堆栈分配内存中的返回地址,因为堆栈指针的值与存储返回地址时的值相同:就
A
而言,
jal B
操作没有改变堆栈顶部,调用
B
后,它位于同一位置,即使
B
也分配(并释放)了一些内存


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

遍历列表的代码是正确的,看起来有点像这样:

...
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附加项
,并且不再需要标签
增加


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


就内存分配而言,您将堆栈上的节点分配给一个数据结构节点,该节点保持函数的实例化