Как изменить порядок строк многострочной строки в MIPS? - PullRequest
1 голос
/ 13 марта 2020

У меня есть многострочное сообщение в форме:

.data
    msg:
        .ascii "aYZ B"
        .byte 10
        .ascii "234"
        .byte 10
        .ascii "b cd A"
        .byte 10

И мне нужно изменить порядок, в котором печатаются строки, так:

aYZ B ---- ------------ b cd A

234 ---- становится --- 234

b cd A ---------- ------ aYZ B

Моя общая идея до сих пор состоит в том, чтобы поместить sh адрес первого символа в стек, а затем выполнить итерацию по сообщению (базовый адрес msg + смещение counter) и pu sh адрес каждого символа сразу после символа '\ n' (.byte 10) в стек (индекс символа \ n '+ 1).

Тогда Я смогу вытолкнуть первую букву в каждой строке из стека в обратном порядке.

С чем я борюсь, так это с тем, как я могу изменить исходное сообщение, когда я oop через него , Должен ли я просто создать новое сообщение в обратном порядке? Если так, то как? Я предполагаю, что я использовал бы конкатенацию строк для этого?

Наконец, как мне распечатать это сообщение? (Я могу использовать syscall 4, но мне нужно, чтобы все сообщение хранилось в одной метке).

РЕДАКТИРОВАТЬ:

Так что мне удалось собрать решение вместе и работает почти правильно. В нем есть одна небольшая ошибка: самая последняя строка сообщения не печатается в отдельной строке, а просто печатается сразу после второй до последней строки.

Если кто-то знает, как это сделать исправить эту маленькую проблему, я хотел бы знать, как.

.data
    key: .byte 1
    msg:
        .ascii "ayZ B"
        .byte 10
        .ascii "234"
        .byte 10
        .ascii "b cD a"
        .byte 10 
.text
main:
    jal reverseLinesAndPrint
    # Exit Program
    li $v0, 10
    syscall

reverseLinesAndPrint:           # $s3 contains address of last char, $s0 contains offsets for both front and back, but must be reset before using for back 
    li $s0, 0                   # RESET value of msg position offset index to iterate from beginning again
    lineLoop:
    add  $s1, $t0, $s0          # Set $s1 equal to the base address of msg ($t0) + the position offset index ($s0)
    lb   $s2, ($s1)             # Deref. and move the current char into s2 for checking
    bne $s2, $zero, notLastChar # If the current char is not the last char in the msg, keep looping
        subi $s3, $s1, 1        # Subtract 1 from the ADDRESS of $s1 to get the last char ('\n') before the NULL Terminator and store it in $s3
        j lastCharIndexFound    # Exit the loop by jumping past it
    notLastChar:
    addi $s0, $s0, 1            # Increment the position offset index
    j lineLoop

    lastCharIndexFound:         # We now have the address of the last valid char in message (always '\n') stored in $s3
    li $s0, 0                   # RESET value of msg position offset index to iterate from ending this time
    reverseLineLoop:
    sub $s1, $s3, $s0           # This time, we are going to subtract from the starting address so we can iterate backwards over msg
    bne $t0, $s1, notFirstChar  # If we iterate all the way to the very first char in msg, exit the loop
        li $v0, 4               # Since the first char doesn't have a '\n' char, we have to manually print
        move $a0, $s1
        syscall
        j exit                  
    notFirstChar:
    lb $s2, ($s1)               # Deref. and move the current char into s2 for checking
    bne $s2, 10, notNLChar      # If we find a '\n' char, we need to do some logic
        li $v0, 4               # First we need to call a syscall to print string (it will stop on the previous '\n' which is now NULL)
        move $a0, $s1
        syscall
        sb $zero, ($s1)         # Second, we need to replace that current '\n' char with NULL
    notNLChar:                  # If the current char is not '\n', keep looping
    addi $s0, $s0, 1            # Increment the offset
    j reverseLineLoop           # Jump to next iteration

exit:
    jr $ra  # Jump back to main

1 Ответ

0 голосов
/ 13 марта 2020

Я думаю, что вы используете новую строку перед строкой, чтобы отделить ее от ранее напечатанной строки. Это умная идея и более эффективная, чем печать только строки (без предшествующей новой строки). В противном случае вы должны будете сделать отдельный системный вызов print-single-char, например syscall с $v0 = 11 / $a0 = '\n' ( MARS системные вызовы )

Это означает, что ваш вывод это как "\nline3", то "\nline2", et c. оставляя курсор в конце каждой строки.

Но вам нужно выполнить специальный случай самой последней строки (первой входной строки), потому что перед ней нет \n. Вы уже являетесь специальным регистром, поэтому просто напечатайте \n вручную перед ним, как строку, заканчивающуюся для предыдущей строки, с помощью системного вызова print-char.


Другим способом сделать это может быть сохранение 0 над символом после новой строки, в 1($s1), поэтому, когда вы позже достигнете начала этой строки, вы можете напечатать ее как "line2\n" включая перевод строки в конце. (Моя версия этого включена ниже.)

Особый случай становится последней строкой ввода (первой строкой вывода), но на самом деле нормально хранить байт 0 после его новой строки, если у вас есть 0 C -определенная строка неявной длины. Там уже будет один, так что вы можете пропустить это при вводе внешнего l oop, или нет, если удобнее этого не делать.


Без изменения данных: write(1, line, length)

MARS имеет системный вызов write() ($v0=15), который принимает указатель + длину, поэтому вам не нужно, чтобы строки заканчивались 0. Точно так же, как POSIX write(int fd, char *buf, size_t len). дескриптор файла $a0 = 1 является стандартным в MARS4.3 и более поздних версиях.

Когда вы найдете новую строку, вы можете записать эту позицию и продолжать цикл. Когда вы найдете другой, вы можете сделать subu $a2, $t1, $t0 ($ a2 = end - start), чтобы получить длину, и установить $a1, указывая на символ после новой строки.

Таким образом, вы можете печатать куски ваш выбор без необходимости манипулировать входными данными, делая их пригодными для ввода только для чтения или без создания копии для уничтожения чего-то, что вам понадобится позже.


Обзор других материалов / кода :

Ваш код странный; Вы вызываете reverseLinesAndPrint без указания указателя и длины или указателя конца в регистрах в main, так зачем вообще делать его отдельной функцией? Его нельзя использовать повторно.

Обычная вещь, которую нужно сделать, это поместить другую метку в конец вашего блока данных ASCII, чтобы вы могли получить этот адрес в регистр, не просматривая строку, чтобы найти длину , (Особенно потому, что у вас явно нет байта 0 в конце строки, чтобы завершить его. Есть один, потому что вы не поместили другие данные сразу после этого, и MARS оставляет разрыв между данными и кодом, когда вы используйте модель памяти, которая помещает начальный адрес раздела данных в адрес 0.)

И вы даже никогда не используете la $reg, msg. Кажется, вы жестко закодировали его адрес как 0? И вы читаете $t0 без предварительной инициализации. MARS начинается с обнуления всех регистров. (Таким образом, можно пропустить подобные ошибки, потому что это правильный адрес в выбранной вами структуре памяти.)

В обычном соглашении о вызовах MIPS $s регистры сохраняются при вызове («сохраняются»), иначе энергонезависимая. Но ваша функция использует их как временные, не сохраняя и не восстанавливая их. Вместо этого было бы нормально использовать регистры $t (а $ a0..3 и $ v0..1).

Ваши циклы неэффективны: вы можете поместить условную ветвь внизу, как do{}while() , То, как вы пишете циклы, очень неуклюже и включает в себя 2 взятых ветви за l oop итерацию (включая безусловную ветвь l oop). Или 3 для вашего поиска l oop, где вам нужно проверить на \n и на p == end.

// your loops are over-complicated like this:
do {
 loop body;
 if (p == end) {  // conditional branch over the loop epilogue
   stuff;         // put this after the loop instead of jumping over it inside the loop
   goto out;
 }
 counter increment;
} while(1);
out:

Также: напишите где-нибудь блок комментариев, в котором говорится, для чего предназначен каждый регистр. Для некоторых это может быть инструкция, которая инициализирует регистр.

В целом ваши комментарии довольно хороши, в основном они описывают происходящее на более высоком уровне, а не такие вещи, как «добавление 1 к $ s0», которые вы уже можете видеть из настоящей инструкции.


Вот как я это сделал

Я использовал идею перезаписи первого символа строки после его печати. Это байт, за которым следует новой строки. Поэтому, когда мы печатаем строки, они выглядят как line2\n, а не \nline2.

. Я также ставлю метку в конце msg вместо использования strlen l oop. Если вы собираетесь перебирать строку вперед, да, вы должны сохранять указатели где-нибудь (например, в стеке), как вы go, чтобы сохранить работу позже, как вы первоначально думали. Но для строк с постоянным временем сборки, мы можем просто заставить ассемблер сказать нам, где это конец. Я также упаковал строки в одну .ascii строку, чтобы сделать источник более компактным. Я добавил явный терминатор .byte 0 (вместо .asciiz), чтобы иметь метку на терминаторе, а не после.

Я, конечно, использую указатели, а не индексы, поэтому мне не нужен add для индексации внутри l oop. Я использовал lbu в случае, если расширение нуля более эффективно, чем расширение знака до 32-битного. Я предпочел бы думать о значениях char как о маленьких целых числах от 0..255, чем -128..127. Не то чтобы я делал какие-либо подписанные сравнения для них, только для равенства.

Я использовал addiu, потому что я не хочу отлавливать переполнение со знаком при указании математики. Единственная причина когда-либо использовать add вместо addu состоит в том, чтобы перехватить переполнение со знаком.

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

.data
 msg:
    .ascii "ayZ B\n234\nb cD a\n"
 endmsg:         # 0 terminated *and* we have a label at the end for convenience.
    .byte 0

.text
main:
    la   $a0, endmsg
    la   $a1, msg
    jal reverseLinesAndPrint   # revprint(end, start)

    li $v0, 10
    syscall           # exit()

reverseLinesAndPrint:
# $a0 = end of string.  We assume it's pointing at a '\0' that follows a newline
# $a1 = start of string
# $t2 = tmp char
# we also assume the string isn't empty, i.e. that start - end >= 2 on function entry.

# the first char the inner loop looks at is -2(end)

 #move  $t0, $a0          # instead we can leave our args in a0, a1 because syscall/v0=4 doesn't modify them

 lines:                       
   findNL_loop:                    # do {  // inner loop
     addiu  $a0, $a0, -1             # --p
     beq    $a1, $a0, foundStart     # if(p==start) break
     lbu    $t2, -1($a0)                # find a char that follows a newline
     bne    $t2, '\n', findNL_loop   # }while(p[-1] != '\n');
   # $a0 points to the start of a 0-terminated string that ends with a newline.
   foundStart:
    li     $v0, 4
    syscall                        # fputs(p /*$a0*/, stdout)

    sb     $zero, ($a0)            # 0-terminate the previous line, after printing
    bne    $a0, $a1, lines         # } while(!very start of the whole string)

    jr $ra

Протестировано и работает с вашими данными. Не проверено для угловых случаев, таких как пустая первая строка, хотя это работает для пустой последней строки. Я думаю, что во всех случаях я избегаю чтения перед первым символом (за исключением слишком коротких вводов, которые нарушают предварительные условия в комментариях. Вы можете проверить их перед вводом l oop, если хотите обработать их.)

Обратите внимание, что bne $t2, 10, target является псевдоинструкцией. Если бы я оптимизировал больше, я бы поднял это 10 из l oop с li в $t3 или что-то подобное, вместо того, чтобы ассемблер устанавливал эту константу в регистре каждую итерацию. То же самое для li $v0, 4 - этот системный вызов не имеет возвращаемого значения, поэтому он даже не уничтожает $v0.

Использование смещений, таких как -1($a0) в режимах адресации, "бесплатно" - инструкция имеет 16 битов немедленное смещение, так что мы могли бы также использовать его вместо отдельной математической указки.

Я использовал $t2 вместо $t0 без какой-либо реальной причины, просто чтобы иметь уникальный номер для каждого регистра, который я использовал для человека удобочитаемость шрифтом MARS smalli sh.

...