(MIPS)アセンブリ言語でポインタを操る

 状況

アセンブリ言語をやっていて、領域に対しての操作が複数あり、特にループ文の中でポインタを操る方法をまとめました。

言葉で説明すると、データセグメントで、領域を確保し、その後、テキストセグメントで命令を実行します。

    .data
s:  .space 20       #領域確保
nl: .asciiz "\n"    #改行
    .text
    .globl main
main:
  add $sp, $sp, -4  # 退避場所の確保
  sw  $ra, 0($sp)   # 退避
  la  $a0, s        # $a0 <- sのポインタ
  sw  $zero, 0($a0) # $a0 <- 0
  addi $a0, $a0, 4  # $a0<- $a0 + 4
  sw  $zero, 0($a0) # $a0 <- 0
  li $a1,0          #5回printを繰り返す
  li $a2,20          #5回printを繰り返す
  jal print         #print
  lw  $ra, 0($sp)   #戻す
  add $sp, $sp, 4   #退避場所からの移動
  jr $ra
print:
  la $t0,s          # $t0 <- sの領域アドレス
  add $t0,$t0,$a1   # sのアドレス+α
  lw $a0,0($t0)     # $a0 <- $t0のアドレスの値
  li $v0, 1         # syscall 用
  syscall           # 整数値($a0)の表示
  la $a0, nl        # 改行用
  li $v0, 4         # syscall 用
  syscall           # 改行の表示
  add $a1,$a1,4     # ループ用のインデックス
  bne $a1,$a2 print # ループ
  jr $ra

このとき、領域のポインタを変更する方法としていくつかの方法が挙げられる。(領域がint型(4バイト)であるとする。)

  1. 毎回pの指す場所を変える。
  2. 1の改良版(即値の利用)
  3. *(p+i)を使い、代入時に位置を指定する

1 毎回pの指す場所を変える。

実行前にp=p+1のようなコードを書くことにより実現する。

int型は4バイトであるので、(p+1)はp+4を意味する必要がある。

c言語

int *a=&s;  //aに領域のアドレスを入れる
*a=0;       //領域の0番目 <- 0
a=a+1;      //(1は自動的に4として扱われる)
*a=0;       //領域の4番目 <- 0

アセンブリ言語

la $a0, s           # $a0 <- sのポインタ
sw $zero ,0($a0)    #領域の0番目 <- 0
addi $a0,$a0,4      #領域の先頭から4番目を指す
sw $zero ,0($a0)    #領域の4番目 <- 0

2. 1からaddiを削除する(即値の利用)

c言語

int *a=&s;  //aに領域のアドレスを入れる
*a=0;       //領域の0番目 <- 0
*(a+1)=0;   //!!変更点!!

アセンブリ言語

la $a0, s           # $a0 <- sのポインタ
sw $zero ,0($a0)    #領域の0番目 <- 0
sw $zero ,4($a0)    #領域の4番目 <- 0

このやり方は、値を入れる回数が整数値(即値)でわかるときのみ有効であり、関数を作成したときの実用性的には若干低めである。

3. *(p+i)を使い、代入時に位置を指定する

その前に一度変数を使ったやり方

c言語

int *a=&s; //aに領域のアドレスを入れる
i=0;       //初期化
*a=0;      //領域の0番目 <- 0
i=i+4;     //i=4
a=a+i;     //領域の先頭から4番目を指す
*a=0;      //領域の4番目 <- 0
a=a-i;     //領域の先頭から0番目を指す
i=i+4;     //i=8
a=a+i;     //領域の先頭から8番目を指す
*a=0;      //領域の8番目 <- 0
a=a-i;     //領域の先頭から0番目を指す

アセンブリ言語

la $a0, s       # $a0 <- 領域のポインタ
li $t0,0        # $t0 = 0  (= i)
sw $zero ,0($a0)# 領域の0番目 <- 0
addi $t0,$t0,4  # $t0 = 4
add $a0,$a0,$t0 # 領域の先頭から4番目を指す
sw $zero ,0($a0)# 領域の4番目 <- 0
sub $a0,$a0,$t0 # 領域の先頭から0番目を指す

subしてaddしてと無駄が多いのではないかと思うかもしれないが、*(p+i)等のポインタに対して、変数を渡して、ポインタの指す位置を変更することを実現するためには必要なことである。 今回、毎回subする形にしているが、これにより、pの値をほかの場所で使う際も気にせずに使うことができる。(独立性の確保)

本題

c言語

int *a=&s;// aに領域のアドレスを入れる
int i=0;  // 初期化
*a=0;     // 領域の0番目 <- 0
i=i+1;    // *(a+i)と書いたときにiの値は強制的に4倍される
*(a+i)=0; // 領域の4番目 <- 0
i=i+1;    // i = 2
*(a+i)=0; // 領域の8番目 <- 0

同じコードを繰り返しているつまり、、

c言語

int *a=&s;      //aに領域のアドレスを入れる
int i=0;        //初期化
*a=0;           //領域の0番目 <- 0
for(i=0;;i++){  //条件式なし
*(a+i)=0;       //領域のi\*4番目 <- 0
}

ここでiの加算値を1にしておくことで条件式等を考えるときに楽である。

これをアセンブリ言語にすると以下のようになる。

アセンブリ言語

la $a0, s       # $a0 <- 領域のポインタ
li $t0,0;       # $t0 = 0  (= i)
sw $zero ,0($a0)# 領域の0番目 <- 0
addi $t0,$t0,1  # $t0 = 1
mult $t0, 4     # $t0 * 4
mflo $t2        # $t2 = 4
add $a0,$a0,$t2 # 領域の先頭から4番目を指す
sw $zero ,0($a0)# 領域の4番目 <- 0
sub $a0,$a0,$t2 # 領域の先頭から0番目を指す
addi $t0,$t0,1  # $t0 = 2
mult $t0, 4     # $t0 * 4
mflo $t2        # $t2 = 8
add $a0,$a0,$t2 # 領域の先頭から8番目を指す
sw $zero ,0($a0)# 領域の8番目 <- 0
sub $a0,$a0,$t2 # 領域の先頭から0番目を指す

indexの値を常に4ずつ上げる

アセンブリ言語

la $a0, p       # $a0 <- 領域のポインタ
li $t0,0        # $t0 = 0  (= i)
sw $zero ,0($a0)# 領域の0番目 <- 0
addi $t0,$t0,4  # $t0 = 4
add $a0,$a0,$t0 # 領域の先頭から4番目を指す
sw $zero ,0($a0)# 領域の4番目 <- 0
sub $a0,$a0,$t0 # 領域の先頭から0番目を指す
addi $t0,$t0,4  # $t0 = 8
add $a0,$a0,$t0 # 領域の先頭から8番目を指す
sw $zero ,0($a0)# 領域の8番目 <- 0
sub $a0,$a0,$t0 # 領域の先頭から0番目を指す

まとめ

  • 領域に対する操作で様々な方法で領域に文字を代入した。
  • 即値を使うやり方は、領域に入れたい値(定数で)と位置が確定しているのであればとても有効である。
  • multを用いたやり方が汎用性は高いと思われる。