Я думаю, что вы используете новую строку перед строкой, чтобы отделить ее от ранее напечатанной строки. Это умная идея и более эффективная, чем печать только строки (без предшествующей новой строки). В противном случае вы должны будете сделать отдельный системный вызов 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.