这是我第一次使用汇编,我正在尝试实现一个链表。 每个节点有两个字--第一个是节点的值,第二个是列表中下一个节点的地址。对于最后一个节点,NEXT为零。 列表的底部是包含第一个节点地址的单词,如果列表为空,则为0。
我正在尝试实现函数&Add Item&Quot;,其中第一个参数($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 addItem
,并且也不再需要标签increase
。
为了进一步改进,您的主要列表对象可以保留指向列表末尾的指针。虽然在向列表添加节点和从列表中删除节点时需要更多维护,但它将消除查找列表末尾的搜索循环。
就内存分配而言,您是在为持久化函数实例化的数据结构节点分配堆栈上的节点。这在短期内可能是可行的,但从长期来看,在运行时堆栈上分配持久数据结构非常容易出错。*函数应该返回到其调用方,堆栈的顶部(即堆栈指针的值)位于它被调用时的相同位置。如果此函数的任何调用方以正常方式使用了堆栈,他们将无法找到它们基于堆栈的变量/存储。
mars&;QTSPIM没有正确的malloc
和free
函数,但这些函数应该用于永久内存分配。这些模拟器有sbrk
可以替代malloc
,但没有相应的free
操作。(我们可以说把合适的malloc
和free
留给读者练习;)
您可以更改堆栈头,但必须将其恢复到返回时的位置。通过这种方式,堆栈用于其生存期与函数调用的持续时间匹配的任何存储需求。
例如,假设main
调用A
,A
调用B
,B
调用C
。因此,当main
使用jal A
时,它传递A
中一个(从C中隐藏)参数,该参数告诉A
如何返回。但是,在A
结束之前,它调用B
,使用jal 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
操作没有改变堆栈顶部,它在调用B
后位于相同的位置-尽管B
也分配了(并释放)一些内存。
总而言之,在许多情况下,函数需要一些本地存储,其生存期与函数调用的持续时间完全匹配。堆栈是一种廉价的内存分配机制,以满足这些需求。