Перебор связанного списка в MIPS

Я впервые использую сборку и пытаюсь реализовать связанный список. Каждый узел — это 2 слова — первое — это значение узла, а второе — адрес следующего узла в списке. Для последнего узла следующий равен нулю. Основой списка является слово, содержащее адрес первого узла, или 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

person ronihm    schedule 29.04.2021    source источник


Ответы (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 не имеют правильных функций malloc и free, но именно их следует использовать для постоянного выделения памяти. В этих симуляторах есть sbrk, который может заменить malloc, но нет соответствующей операции free. (Можно сказать, что правильные malloc и free оставлены в качестве упражнения для читателя;)

Вы можете изменить голову стека, но должны восстановить ее там, где она была по возвращении. Таким образом, стек используется для любых потребностей в хранении, время жизни которых соответствует продолжительности вызова функции.

Например, предположим, что main звонит A, A звонит B, а B звонит C. Таким образом, когда main использует jal A, он передает A (скрытый от C) параметр в $ra, который сообщает A, как вернуться обратно к main. Однако, прежде чем 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 также извлекает адрес возврата, сохраненный в памяти, выделенной стеку, который он может найти, потому что указатель стека имеет то же значение, что и при сохранении адреса возврата: операция jal B, что касается A, не изменила вершину стека. , он находится там же после вызова B — хотя B тоже выделил (и освободил) часть памяти.

Таким образом, существует ряд сценариев, в которых функции требуется некоторое локальное хранилище, время жизни которого точно соответствует продолжительности вызова функции. Стек — это механизм недорогого выделения памяти для удовлетворения этих потребностей.

person Erik Eidt    schedule 29.04.2021
comment
Спасибо! Что касается распределения памяти - это хороший момент, который я не совсем понимаю. Где правильно хранить данные в долгосрочной перспективе? Кроме того, если я не должен менять голову стека, какой в ​​этом смысл? он используется только для временных операций? - person ronihm; 29.04.2021
comment
Я добавил некоторые дополнительные материалы, чтобы ответить на эти вопросы. - person Erik Eidt; 29.04.2021
comment
Огромное спасибо! - person ronihm; 29.04.2021