私はMIPSアセンブリ言語にまったく慣れておらず、現在、MIPSコーディングに関する大きなセクションがあるコンピューターアーキテクチャのクラスを受講しています。私は過去に他のいくつかの高級プログラミング言語(C、C#、Python)を研究したことがあるので、プログラミングの基礎がいくつかあります。
ここでの私の質問は具体的に尋ねます:MIPSはスタック内の配列にメモリをどのように割り当てますか?私はまだMIPS言語とそのアーキテクチャの概念を概念化することに少し取り組んでいるので、この質問に答えることでMIPSをよりよく理解できるようになることを願っています。この点でも、ポインタがどのように機能するのかよくわかりません...
誰かがこの混乱した学生を助けるために時間をかけることができれば素晴らしいでしょう! :)
MIPSには、Cと同様に、基本的に3つの異なるメモリ割り当て方法があることに注意してください。
次のCコードについて考えてみます。
int arr[2]; //global variable, allocated in the data segment
int main() {
int arr2[2]; //local variable, allocated on the stack
int *arr3 = malloc(sizeof(int) * 2); //local variable, allocated on the heap
}
MIPSアセンブリは、これらすべてのタイプのデータをサポートします。
データセグメントにint配列を割り当てるには、次を使用できます。
.data
arr: .Word 0, 0 #enough space for two words, initialized to 0, arr label points to the first element
スタックにint配列を割り当てるには、次を使用できます。
#save $ra
addi $sp $sp -4 #give 4 bytes to the stack to store the frame pointer
sw $fp 0($sp) #store the old frame pointer
move $fp $sp #exchange the frame and stack pointers
addi $sp $sp -12 #allocate 12 more bytes of storage, 4 for $ra and 8 for our array
sw $ra -4($fp)
# at this point we have allocated space for our array at the address -8($fp)
ヒープにスペースを割り当てるには、システムコールが必要です。 spimシミュレータでは、これは システムコール9 :です。
li $a0 8 #enough space for two integers
li $v0 9 #syscall 9 (sbrk)
syscall
# address of the allocated space is now in $v0
他のアーチとは異なり、MIPSにはプッシュまたはポップレジスタ/即時命令がありません。したがって、スタックを自分で管理することに依存します。これは、レジスタが特定の用途を持たないmul/div以外のほとんどのArchで実際に指摘されており、推奨される使用方法にすぎません。これで、好きなように使用した場合、たとえばCと統合しようとすると、何かが壊れてしまいます。
何かをスタックにプッシュするには、ストア命令を使用する必要があります。これらはsb, sh, sw, swl, swr
です。それぞれバイト、ハーフ、ワード、ワード左、ワード右。
addiu $sp, $sp, -4 # Push stack 1 Word
sw $t0, 0($sp) # place item on newly pushed space
スタックから何かをポップするには、addiuでそれをデインクリメントする必要があります。ただし、lb, lh, lw, lwl, lwr
を使用してデータをロードすることもできます。
lw $t0, 0($sp)
addiu $sp, $sp, 4 # pop stack 1 Word
これは、2つのワードプッシュで使用する例です。
addiu $sp, $sp, -8 # allocate two words
sw $t0, 0($sp) # Push two registers t0 t1
sw $t1, 4($sp)
lw $t1, 4($sp) # pop two registers t0 t1
lw $t0, 0($sp)
addiu $sp, $sp, 8 # deallocate two words
これは、リーフ以外の関数の呼び出しが混乱しないように、リターンアドレスに使用する例です。
# grab us a quick string
.data
example_str: .asciiz "hello world :^)"
# grab us a function
.text
.globl example
.type test, @function
test:
addiu $sp, $sp, -4 # Push stack for 1 Word
sw $ra, 0($sp) # save return address
la $a0, example_str # call puts and give it a string
jal puts
nop
lw $ra, 0($sp) # load return address
addiu $sp, $sp, 4 # pop stack for 1 Word
jr $ra # return from function to caller
nop
これは、複数の要素を連続してプッシュする例です。もちろん、ポップはその逆です。
.data
example_arr: .Word 0, 0, 0, 0
.text
addiu $sp, $sp, -16
la $t0, example_arr
lw $t1, 0($t0)
sw $t1, 0($sp)
lw $t1, 0($t0)
sw $t1, 4($sp)
lw $t1, 0($t0)
sw $t1, 8($sp)
sw $t1, 12($sp)
これは、malloc/callocの使用例です。
# grab us a function
.text
.globl example
.type test, @function
test:
addiu $sp, $sp, -4 # Push stack for 1 Word
sw $ra, 0($sp) # save return address
li $a0, 4 # allocate 4*4 bytes (16)
li $a1, 4
jal calloc
nop
addiu $sp, $sp, -4 # Push stack for 1 Word
sw $v0, 0($sp) # save calloc'd buffer
move $t0, $v0 # get the buffer into a temp
li $t1, 1 # fill some temps with numbers
li $t2, 2
li $t3, 3
li $t4, 4
sw $t1, 0($t0) # save some temps to buffer
sw $t2, 4($t0)
sw $t3, 8($t0)
sw $t4, 12($t0)
... do stuff with the buffer ...
lw $a0, 0($sp) # pop buffer from stack
jal free # run it through free
nop
addiu $sp, $sp, 4 # don't forget to decrement
lw $ra, 0($sp) # load return address
addiu $sp, $sp, 4 # pop stack for 1 Word
jr $ra # return from function to caller
nop
前に述べたように、明確に定義された特定の用途はありません。したがって、独自のスタックを使用して、必要に応じて$ spの使用を忘れることもできます。 $ t *を$ s *として使用した例を示しました。これは、たとえば、各関数に独自のスタックを強制する場合や、考えられる他のユースケースの場合に機能します。一例として、Lua( https://lua.org )はこれをある程度行います。ただし、MIPSではありません。複数のスタックは、特に複数の目的を扱うときに便利です。