次の形式の複数行のメッセージがあります。
.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
これまでの私の一般的な考え方は、最初の文字のアドレスをスタックにプッシュしてから、メッセージ(msgのベースアドレス+オフセットカウンター)を繰り返し処理し、 '\ n'文字(.byte)の直後にすべての文字のアドレスをプッシュすることです。 10)スタックに( '\ n'char + 1のインデックス)。
次に、各行の最初の文字を逆の順序でスタックからポップできるようになります。
私が苦労しているのは、元のメッセージをループしながら変更する方法です。新しいメッセージを逆の順序で作成する必要がありますか?もしそうなら、どのように?これには文字列連結を使用すると思いますか?
最後に、そのメッセージを印刷するにはどうすればよいですか?(syscall 4を使用できますが、メッセージ全体を1つのラベルに保存する必要があります)。
編集:
だから私はなんとか解決策をまとめることができ、ほぼ正しく機能しました。小さなバグが1つあります。メッセージの最後の行がそれ自体の行に印刷されず、最後から2番目の行の直後に印刷されるだけです。
誰かがその小さな問題を修正する方法を知っているなら、私はその方法を知りたいです。
.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
以前に印刷した行から改行を区切るために、行の前に改行を使用していると思います。これは賢いアイデアであり、(前の改行なしで)行だけを印刷するよりも効率的です。それ以外の場合はsyscall
、$v0 = 11
/ $a0 = '\n'
(MARSシステムコール)のように、個別のprint-single-charシステムコールを行う必要があります。
これは、各行の終わりにカーソルを置いたまま、出力が"\nline3"
then"\nline2"
などのようになることを意味します。
ただし、最後の行(入力文字列の最初)は\n
前にないため、特殊なケースにする必要があります。あなたはすでにそれを特別なケーシングにしているので\n
、前の行の終わりの行として、print-char syscallを使用して、その前に手動で印刷するだけです。
これを行う別の方法は、改行の後0
に文字の上にを格納する1($s1)
ことです。したがって、後でこの行の先頭に到達したとき"line2\n"
に、末尾に改行を含めるように印刷できます。(これの私のバージョンは以下に含まれています。)
特殊なケースが入力の最終行(出力の最初の行)になり0
ますが、0で終了するCスタイルの暗黙的な長さの文字列がある場合は、改行の後にバイトを格納することは実際には問題ありません。すでにそこにあるので、外側のループに入るときにそれをスキップするか、そうでない方が便利な場合はスキップすることができます。
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
、文字列の最後に明示的に終了するバイトがないためです。直後に他のデータを配置しなかったために1つあり、MARSはメモリを使用するときにデータとコードの間にギャップを残しますデータセクションの開始アドレスをアドレス0に配置するモデル。)
そして、あなたは決して使用しませんla $reg, msg
。アドレスを次のようにハードコーディングしているよう0
です。そして$t0
、最初に初期化せずに読みます。MARSは、すべてのレジスタをゼロにして開始します。(したがって、このようなバグは、選択したメモリレイアウトの有効なアドレスであるため、見逃す可能性があります。)
通常のMIPS呼び出し規約では、$s
レジスターは呼び出し保存(「保存」)され、別名不揮発性です。ただし、関数はそれらを保存/復元せずに一時的なものとして使用します。$t
代わりにレジスタ(および$ a0..3と$ v0..1)を使用するのが普通です。
ループは非効率的ですdo{}while()
。条件分岐をのように下部に配置できます。ループを作成する方法は非常に不格好で、ループの反復ごとに2つの分岐が必要です(無条件のループ分岐を含む)。それともあなたがチェックする必要があり、検索のためのループ3\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:
また、各レジスタの目的を示すコメントのブロックをどこかに書き込みます。一部の場合は、レジスタを初期化する命令上にある可能性があります。
一般的に、あなたのコメントはかなり良いものであり、実際の指示からすでにわかる「$ s0に1を追加する」のようなものではなく、何が起こっているのかをより高いレベルで説明しています。
印刷後に行の最初の文字を上書きするというアイデアを使用しました。これは、改行に続くバイトです。したがって、行を印刷するとき、それらはそうではline2\n
ないようです\nline2
。
またmsg
、strlenループを使用する代わりに、の最後にラベルを付けました。文字列を前向きに繰り返す場合は、最初に考えていたように、後で作業を保存するときに、ポインタをどこかに(スタックなどに)保存する必要があります。ただし、アセンブル時定数文字列の場合は、アセンブラーに終了先を教えてもらうだけです。また.ascii
、ソースをよりコンパクトに保つために、行を1つの文字列にパックしました。.byte 0
(の代わりに.asciiz
)明示的なターミネーターを追加したので、ターミネーターの後にではなくラベルを付けることができました。
もちろん、インデックスではなくポインタを使用するのでadd
、ループ内でインデックスを作成する必要はありませんでした。lbu
32ビットへの符号拡張よりもゼロ拡張の方が効率的である場合に使用しました。char値は、-128..127ではなく0..255の小さな整数と考えたいと思います。平等のためだけに、署名された比較を行うわけではありません。
addiu
ポインタ計算で符号付きオーバーフローをトラップしたくないので、使用しました。add
代わりに使用する唯一の理由addu
は、署名されたオーバーフローをトラップすることです。
内側のループでは、両方の終了条件をチェックするために2つの条件付き分岐が必要ですが、これは、慎重に計画してこのようなループを作成する方法のコンパクトで効率的な例です。
.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
テストされ、データを処理します。空の最初の行のようなコーナーケースではテストされていませんが、空の最後の行では機能します。すべての場合で最初の文字の前を読むことは避けていると思います(コメントの前提条件に違反する短すぎる入力を除きます。処理したい場合は、ループに入る前にそれらを確認できます)。
これbne $t2, 10, target
は疑似命令であることに注意してください。さらに最適化する場合10
は、反復ごとにアセンブラーにレジスターにその定数を設定させるのではなく、li
into$t3
などを使用してループから引き上げます。li $v0, 4
-についても同じです。syscallには戻り値がないため、破棄すらしません$v0
。
-1($a0)
アドレッシングモードのようにオフセットを使用するのは「無料」です。命令には16ビットの即時変位があるため、個別のポインタ計算の代わりに使用することをお勧めします。
私は本当の理由$t2
で$t0
はなく、MARSの小さいフォントで人間が読みやすくするために使用していたすべてのregに一意の番号を付けるために使用しました。
この記事はインターネットから収集されたものであり、転載の際にはソースを示してください。
侵害の場合は、連絡してください[email protected]
コメントを追加