(MIPS) 初学者向け SIPMで領域を扱う

内容

データセグメントにてメモリ(領域)を確保する際に、その扱い方をします。 (つまり、グローバル変数でメモリ(領域)を定義します。)

メモリに対して使う主な命令(32bit機とする,64bit機なら1Word=8byte)

命令 使用例 意味
la la $rt,即値($rs) $rtに$rsのアドレスを入れる
lw lw $rt,即値($rs) $rtに$rsの1Word(4byte)分を入れる
sw sw $rt,即値($rs) $rsから1Word(4byte)分を$rtを入れる
lb lb $rt,即値($rs) $rtに$rsの1byte分を入れる
sb sb $rt,即値($rs) $rsから1byte分を$rtを入れる

補足

  • laは$rt=&rsと同等である。

  • char*s[10]ならchar型は1byteであるから、lb,sbを多用する

  • int*s[10]ならint型は4byteであるから、lw,swを多用する

こんな感じである

intにおけるメモリの使い方。

取得、変更、表示を行う。

まずはc言語で記述

c言語

int s[4];
void print(int n) { //print関数
  int i = 0;
  for (i = 0; i < 4; i++) {
    printf("%d %d\n", &s[i], s[i]); //アドレスと値を表示
  }
}

void main() {
  s[0] = 0;
  s[1] = 0;
  s[2] = 0;
  s[3] = 0;
  print(4); //全て0
  s[0] = 1;
  s[1] = 2;
  s[2] = 3;
  s[3] = 4;
  print(4); //1,2,3,4を順に表示
}

結果(c言語)

#アドレス #値
862081056 0
862081060 0 #アドレスが4ずつ増えている
862081064 0
862081068 0
862081056 1 #アドレスが一番上と同じ
862081060 2
862081064 3
862081068 4

アセンブリ言語

  .data
  s:        .space 16       #領域確保
  sp:      .asciiz " "     #スペース
  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
  sw  $zero, 4($a0)   # $a0 <- 0
  sw  $zero, 8($a0)   # $a0 <- 0
  sw  $zero, 12($a0)  # $a0 <- 0

  li $a1,0            #ループ用のindex
  li $a2,16           #ループ用の限界値(ループ回数)
  jal print           #print

  la $a0,s            # $a0 <- sのポインタ
  li $t0,1            # $t0 = 1
  sw  $t0, 0($a0)     # $a0 <- 1
  addi $t0, $t0, 1
  addi $a0, $a0, 4    # $a0++4
  sw  $t0, 0($a0)     # $a0 <- 2
  addi $t0, $t0, 1
  addi $a0, $a0, 4    # $a0++ 4
  sw  $t0, 0($a0)     # $a0 <- 3
  addi $t0, $t0, 1
  addi $a0, $a0, 4    # $a0++ 4
  sw  $t0, 0($a0)     # $a0 <- 4

  li $a1,0            #ループ用のindex
  li $a2,16           #ループ用の限界値(ループ回数)
  jal print           #print

  lw  $ra, 0($sp)     #戻す
  add $sp, $sp, 4     #退避場所からの移動
  jr $ra

print:
  la $a0,s            # $t0 <- sの領域アドレス
  add $a0,$a0,$a1     # sのアドレス+α
  move $t0, $a0       # $t0に$a0の保存
  la $a0,0($a0)       # $a0 <- $t0のアドレス
  li $v0, 1           # syscall用
  syscall             # 整数値($a0)の表示
  la $a0, sp          # スペース用
  li $v0, 4           # syscall用
  syscall             # スペースの表示
  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

結果(アセンブリ言語)

#アドレス #値
268500992 0
268500996 0 #アドレスが4ずつ増えている
268501000 0
268501004 0
268500992 1 #アドレスが一番上と同じ
268500996 2
268501000 3
268501004 4

注意点

  • laとlwをしっかりと使い分ける
  • 4byte分をしっかりと加算する
  • アドレスがc言語アセンブリ言語共に4ずつ増えている。
  • lwやswの即値の部分にレジスタの値(変数)は使えないので、即値($rs)の$rsの部分をループごとに加算していくようにする
  • $a0は一つしかなく、syscallで毎回使うので、アドレスの位置をずらすときは$a0に直接加算するのではなく、レジスタを使う。もし直接$a0に加算するなら、改行等のsyscallの時に退避させる。

charにおけるメモリの使い方。

取得、変更、表示を行う。

まずはc言語で記述

c言語

#include <stdio.h>
char s[] = "abcd";

void print(int n) {  // print関数
  int i = 0;
  for (i = 0; i < 4; i++) {
    printf("%d %s\n", &s[i], (s + i));  //アドレスと値を表示
  }
}

void main() {
  print(4);
  // abcdと表示
  s[0] = 'A';
  s[1] = 'B';
  s[2] = 'C';
  s[3] = 'D';
  print(4);  // ABCDと表示
}

結果(c言語)

#アドレス #値
1104089104 abcd
1104089105 bcd #アドレスが1ずつ増えている
1104089106 cd
1104089107 d
1104089104 ABCD #アドレスが一番上と同じ
1104089105 BCD
1104089106 CD
1104089107 D

アセンブリ言語

  .data
  before:      .asciiz "abcde"
  .asciiz "A"
  .asciiz "B"
  .asciiz "C"
  .asciiz "D"
  sp:      .asciiz " "     #スペース
  nl:      .asciiz "\n"    #改行

  .text
  .globl main
main:
  add $sp, $sp, -4    # 退避場所の確保
  sw  $ra, 0($sp)     # 退避

  la  $a0, before     # $a0 <- beforeのポインタ

  li $a1,0            #ループ用のindex
  li $a2,4            #ループ用の限界値(ループ回数)
  jal print           #print

  la $a0,before       # $a0 <- beforeのポインタ
  lb $t0,after_1      # $a0 <- after_1のポインタ
  sw  $t0, 0($a0)     # $a0 <- "A"
  addi $t0, $t0, 1    # "A"の次の"B"を指す
  addi $a0, $a0, 1    # $a0++1
  sb  $t0, 0($a0)     # $a0 <- "B"
  addi $t0, $t0, 1    # "B"の次の"C"を指す
  addi $a0, $a0, 1    # $a0++ 1
  sb  $t0, 0($a0)     # $a0 <- "C"
  addi $t0, $t0, 1    # "C"の次の"D"を指す
  addi $a0, $a0, 1    # $a0++ 1
  sb  $t0, 0($a0)     # $a0 <- "D"

  li $a1,0            #ループ用のindex
  li $a2,4           #ループ用の限界値(ループ回数)
  jal print           #print

  lw  $ra, 0($sp)     #戻す
  add $sp, $sp, 4     #退避場所からの移動
  jr $ra

print:
  la $a0,before       # $t0 <- beforeの領域アドレス
  add $a0,$a0,$a1     # beforeのアドレス+α
  move $t0, $a0       # $t0に$a0の保存
  la $a0,0($a0)       # $a0 <- $t0のアドレス
  li $v0, 1           # syscall用
  syscall             # 整数値($a0)の表示
  la $a0, sp          # スペース用
  li $v0, 4           # syscall用
  syscall             # スペースの表示
  move $a0,$t0       # $a0 <- $t0のアドレスの値
  syscall             # 整数値($a0)の表示
  la $a0, nl          # 改行用
  syscall             # 改行の表示
  add $a1,$a1,1       # ループ用のインデックス
  bne $a1,$a2 print   # ループ
  jr $ra

結果(アセンブリ言語)

#アドレス #値
268500992 abcd
268500993 bcd #アドレスが4ずつ増えている
268500994 cd
268500995 d
268500992 ABCD アドレスが一番上同じ
268500993 BCD
268500994 CD
268500995 D

注意点

  • lalbをしっかりと使い分ける
  • 1byte分をしっかりと加算する
  • アドレスがc言語アセンブリ言語共に1ずつ増えている。
  • データセグメントはつながっている。(名前を指定しないでafter_1のみをロードして、後はそのアドレスに1を加算していたがうまくいっている)

まとめ

  • la,lw,sw,sb,lbをしっかりと使い分ける必要がある。
  • intは4byte,charは1byteずつ移動していることに注目する