Assembly 在MIPS中对链表进行迭代
这是我第一次使用汇编,我正在尝试实现一个链表。 每个节点有2个字-第一个是节点的值,第二个是列表中下一个节点的地址。对于最后一个节点,next为零。 列表的底部是一个包含第一个节点地址的字,如果列表为空,则为0 我正在尝试实现函数“additem”,其中第一个参数($a0)是列表底部的地址,$a1是存储我要添加到列表中的值的地址。 为了做到这一点,我尝试遍历列表并找到最后一项,因此我将其“next”值设置为我创建的新节点 我觉得这很难看(我同时使用了“循环”和“增加”),我错过了一种更简单的方法来遍历列表,但我有点困惑如何正确地使用一个循环,因为我想在列表结束前停止一步 实现这一目标的更好方法是什么 谢谢Assembly 在MIPS中对链表进行迭代,assembly,mips,Assembly,Mips,这是我第一次使用汇编,我正在尝试实现一个链表。 每个节点有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附加项
,并且不再需要标签增加
为了进一步改进,您的主列表对象可以保留一个指向列表末尾的指针。虽然随着节点在列表中的添加和删除,这将需要更多的维护,但它将消除查找列表末尾的搜索循环
就内存分配而言,您将堆栈上的节点分配给一个数据结构节点,该节点保持函数的实例化