さて、いくつか問題がありました。
修正したコードは一番下にあります。C コードからの変換がどの程度リテラルである必要があるかに基づいて、実際には 2 つの方法があります。概念上の問題の一部は、2 つの方法のパーツを組み合わせようとしていたことかもしれません。
[重複として] コメント フィードバックに基づいて、元のコードの最初の 2 つの命令を 1 つにまとめました (例: li
then addi
)。ただし、1 つだけを使用する場合は、レジスタをそれ自体に追加するli
ため正しいaddi
ですが、初期値がゼロであることに依存することはできません。
値がゼロのsll
レジスタをシフトしているため、inst は何もしていません。
でロードt1
するには、 [または]a0
を使用しますadd $t1,$a0,0
add $t1,$a0,$zero
役に立たlw
なかった [C コードがロードされないa
のに、なぜ asm を使用する必要があるのか?]。
しかし、ループがまだ正しく機能していなかったので、これを少し変更しました。
あなたの後に戻ることはなかったblt
ので、ループが機能したとしても、それは「世界の端から落ちる」でしょう. 呼び出される各asm ルーチン [ のように呼び出されるものjal set
] には、明示的なreturn ステートメントが必要です。jr $ra
注: MIPS asm では、a*
[引数レジスタ]はcalleeによって変更される可能性があるため、代わりにループします(つまり、callerはそれらが破棄されることを期待しています)。a0
t1
とにかく、修正されたコードは次のとおりです [不要なスタイルのクリーンアップをご容赦ください]:
.text
.globl set
set:
li $t0,0 # i = 0
L1:
sw $a2,0($a0) # a[i] = v
add $a0,$a0,4 # advance pointer
add $t0,$t0,1 # ++i
blt $t0,$a1,L1 # continue the loop as long as i < n
jr $ra # return
元の C 関数が次のようなものだったとします。
int
set(int *a, int n, int v)
{
int *end;
end = a + n;
for (; a < end; ++a)
*a = v;
return n;
}
次に、これはより文字通りの翻訳になります。
.text
.globl set
set:
sll $a1,$a1,2 # convert int count to byte length
add $a1,$a0,$a1 # end = a + length
L1:
sw $a2,0($a0) # *a = v
add $a0,$a0,4 # ++a
blt $a0,$a1,L1 # continue the loop as long as a < end
jr $ra # return
IMO、どちらの方法も元の C 関数の許容可能な実装です。最初のものは、インデックス変数 [の概念] を保持するという点で、より文字通りですi
。ただし、2 番目にない追加の命令があります。
オプティマイザーは、変換する C 関数に関係なく、おそらく同じコード (つまり、2 番目の asm) を生成します (MIPS には、x86
asm のような強力なインデックス アドレッシング モードがありません)。
したがって、どちらが「正しい」かは、あなたの教授がどれだけ執着するかによって異なります。
補足: 2 つの例の間でスタイルが変更されていることに注意してください。つまり、コードの変更はさておき、明確にするためにいくつかの空白行を追加します。
完全を期すために、main
テスト時に作成した関数を次に示します。
.data
arr: .space 400 # allocate more space than count
.text
.globl main
main:
la $a0,arr # get array pointer
li $a1,10 # get count
li $a2,3257 # value to store
jal set
li $v0,1 # exit syscall number
syscall
アップデート:
ループ内にある場合、?の前に行a[i++] = v
を配置しますか?add $t0, $t0, 1
sw $a2, 0($a0)
いいえ、C コードがa[++i] = v
. おそらく、これを確認する最善の方法は、最初に C コードを単純化することです。
a[i++] = v
実際には:
a[i] = v;
i += 1;
そして、a[++i] = v
実際には:
i += 1;
a[i] = v;
現在、C コードの行と asm 命令の間には 1 対 1 の対応があります。
いつ使用しsll
ますか?私は例を読んでいsll $t1, $t0, 2
ましたが、カウンターを使用して配列を通過するときに、人々が通常そうしていることに気付きました。
はい。私の2 番目の実装をよく見るとsll
、そのように使用されています。また、元の C コードが与えられた場合でも、ループをコーディングする方法になります。
lw
Cコードが次のようなことを言った場合、私はを使用しint x = a[0]
ますか?
はい、正確に。
asm のプロトタイプを作成するもう 1 つの方法は、C コードを「非常に馬鹿げた C」に変換することです。
つまりif
、最も単純な形式のみです: if (x >= y) goto label
. 立ち入りif (x < y) j = 10
禁止です。
関数スコープ変数または関数引数変数はありません。レジスタ名であるグローバル変数のみです。
複雑な表現はありません。x = y
、x += y
、またはのような単純なもののみx = y + z
。したがって、a = b + c + d
複雑すぎます。
レジスタ変数は、整数値とバイトポインターの両方として機能します。したがって、ポインターとして使用されているレジスターに追加するときは、バイト ポインターに追加するのと同じように、int
配列をインクリメントするには、 を追加する必要があります4
。
バイト ポインターとポインターの実際の違いint
は、ロード/ストア操作を行う場合にのみ問題になります: lw/sw
forint
とlb/sb
for byte。
したがって、「ダム」として再コーディングされた 2 番目の C 関数は次のとおりです。
// RETURNS: number of elements changed
int
set(void)
// a0 -- "a" (pointer to int array)
// a1 -- "n" (number of elements in "a")
// a2 -- "v" (value to set into "a")
{
v0 = a1; // set return value
a1 <<= 2; // convert int count to byte length
a1 += a0; // point to one past end of array
L1:
*(int *)a0 = a2; // store v at current array location
a0 += 4; // point to next array element
if (a0 < a1) goto L1; // loop back until done
return;
}
更新#2:
最初の実装では、2 番目の実装でのadd $a0, $a0, 4
使用と同等sll
ですか?
そうではありません。覚えておくべき重要なことはCです。ポインターに1を追加する[またはインデックスを付ける]と、コンパイラーは、ポインターが定義されている型の を追加i
するインクリメント/追加命令を生成します。sizeof
つまり、 forint *iptr
を指定するとis 4iptr += 1
が生成さadd $a0,$a0,4
れます。 , があった場合、コンパイラはis 8を生成します。sizeof(int)
double *dptr
dptr += 1
add $a0,$a0,8
sizeof(double)
これは、C コンパイラが提供する強力な「便利さ」です。なぜなら、配列、ポインタ、インデックスを交換可能に使用できるからです。
asm では、C コンパイラが自動的に行うことを手動で行う必要があります。
次のことを考慮してください。配列内の要素数のカウントである値があり、それを と呼びますcount
。ここで、配列が占めるバイト数を知りたいと思います。これを と呼びますlen
。さまざまな型についてそれを決定するための C コードを次に示します。
char *arr;
len = count * sizeof(char);
len = count * 1;
len = count << 0;
// sll $a1,$a1,0
short *arr;
len = count * sizeof(short);
len = count * 2;
len = count << 1;
// sll $a1,$a1,1
int *arr;
len = count * sizeof(int);
len = count * 4;
len = count << 2;
// sll $a1,$a1,2
double *arr;
len = count * sizeof(double);
len = count * 8;
len = count << 3;
// sll $a1,$a1,3
私が理解していることから、sll セット i を int のカウンターとして使用すると、i がインクリメントされ、配列も反復されます
No.は単に MIPS の「論理左シフト」命令であり、C の演算子sll
に相当するものが必要な場合に使用します。<<
あなたが考えているのは、その効果を達成するためにどのようsll
に使用できるかということです。
の配列を反復処理するにはint
、インデックスを 1 増やしますが、配列ポインターも 4 増やします。これが、最初の asm の例で行ったことです。終了条件はindex >= count
.
2 番目の asm の例では、要素数を ( を使用して) バイト長に変換ssl
し、配列アドレスを追加することで、個別のインデックス変数を削除しました。配列$a1
の最後の要素のアドレス + 1 になり、終了条件はcurrent_address >= last_element_plus_1
. current_address ( ) は 4 ( )$a0
ずつインクリメントする必要があることに注意してください。add $a0,$a0,4
覚えておくべき重要なことの 1 つは、asm 命令 [特に MIPS] は単純である (つまり、dumb ) ということです。彼らは一度に 1 つのことしか行いません。ステートメントが十分に複雑な場合、1 つの C 代入ステートメントで約 20 の命令を生成できます。より複雑な結果を生成するのは、asm 命令がどのように組み合わされるかです。