Latexの書き方と基本的な機能

初めに

Latexを書く上で基本的な部分を説明していきます。

documentclass

latexを書き始めるにあたり必要となります。

% \documentclass[オプション]{文書クラス}
\documentclass[a4j,12pt][jarticle]

これは、紙の初期設定を決めます。 例のオプションでは、a4の紙に日本語で書くこと、文字の大きさは12ポイントを使うことを指定します。

文章クラスでは、この文章が何のための文章(論文、報告書、本)なのかを示すものになります。文章クラスにより使えるもの(section,chapter)が変わってくるので注意しましょう。(詳しくはこちらをクリック)

例では、日本語の論文であることを示しています。

以下に文章クラスで基本的なクラスを紹介します。

役割 クラス
論文,記事 article
報告書 report
book

ここに日本語を意味する"j"等が先頭につきます。

begin,end

beginとendはこの中で今から〇〇をしますということを宣言します。(例: 5章目を書きます、数式を書きます、図を入れますetc)

% \begin{やること}
% \end{やること}
\begin{section}
\end{section}

例では、新しい章を始めることを意味しています。

Latexの用語的には環境といい、例のものをsection環境といいます。

document

これはここから文章を書くことを意味します。 つまりこの中に文章を書いていきます。

\begin{document}
\end{document}

usepackage

usepackageではLatexのデフォルトでは入っていない機能,環境を入れていきます。

\usepackage{}は\documentclass[]{}と\begin{document}の間に書きます。

% \usepackage{入れたい機能,環境}
\usepackage{url}

例では、urlの機能(簡単に書く機能)を入れています。(urlには特殊文字が多く含まれるためそれを無効化してくれます。一部無効化されませんが...)

コメントアウト

コメントアウトとはこの文章を「一回消したい!けど後で使うかもしれない」といったときに一時的に文章をないものとしてくれる便利機能です。

使い方は簡単でコメントアウトしたい行の先頭に"%"(パーセント)を付けるだけです。

実は、先ほどから度々登場しています。

数式

∫や...(ドット3つ)は検索された方が早いと思われるので割愛します。

インテグラル latex」等で出てきます。

インラインモード

インラインモードとは文中に数式を入れる形式です。もし数式を1行で表したい場合にはディスプレイモードの方を参照してください。 インラインモードにはいくつかの書き方が存在しています。

  • $数式$
  • (数式)
  • math環境
% $数式$
$ a+b=c $
% \(数式\)
\(a+b=c\)
\begin{math}
%数式
a+b=c
begin{math}

ディスプレイモード

ディスプレイモードとは数式の前と後で改行をして表示するやり方です。数式に番号を付けたい場合にはディスプレイモード(番号付き)を参照してください。 ディスプレイモードにはいくつかの書き方があります。

  • $$数式$$
  • [数式]
  • displaymath環境
% $$数式$$
$$ a+b=c $$
% \[数式\]
\[a+b=c\]
\begin{displaymath}
%数式
a+b=c
begin{displaymath}

ディスプレイモード(番号付き)

番号付きの数式にはいくつかの書き方があります。

種類 特徴
equation 1つだけ書けます
eqnarray 複数書くことができます
\begin{equation}
% 数式
a+b=c
\end{equation}
\begin{eqnarray}
% 数式\\
% 数式
a+b=c\\
x*y=z
\end{eqnarray}

※eqnarrayを使う際には、一番最後の行以外の行の最後の"\"を付けます。(改行を意味します。)

指定の位置で合わせる

複数の行を=等の指定の位置で揃えたい場合には"&"を使います。

\begin{eqnarray}
a+b&=c\\
x*y&=z
\end{eqnarray}

中央に寄せる

中央に寄せたい際にはgatherを使います。

\begin{gather}
a+b=c\\
x*y=z
\end{gather}

新しい書き方(align)

eqnarrayより、alignの方が新しい記述になっています。一部性能が違いますがalignの方が優れていると私は思っております。この際、\usepackage{amsmath}が必要となります。

\begin{align}
a+b=c\\
x*y=z
\end{align}

その他の数式について詳しい記述はこちら qiita.com

参照

参考文献

参考文献の書き方は以下のようになります。

% \cite{好きな文字}
こちらは参考文献のやり方で\cite{test}% ---参考文献の欄------
\begin{thebibliography}{99}
\bibitem{test} ,aさん『参考文献の本』,test社(2017)
\end{thebibliography}

まず、\cite{}の中になにを参考にしているのかわかりやすい文字を入れましょう。

文章には表示されないため自分がわかりやすい名前にするのがよいでしょう。

\begin{thebibliography}{99}で参考文献が始まることを定義しています。99は99個以下の参考文献があることを示しています。

数式の番号の参照

数式の番号を参考にしたいときには以下の2つのやり方があります。

種類 特徴
ref 数字だけ表示します
eqrf 数字を()でくくって表示します

やり方

  1. 参照したい数式の行の一行上に\label{}を付ける
  2. \ref{}または\eqref{}を使って表示する
\usepackage{amsmath}
\begin{align}
% \label{好きな文字}
\label{test1}
a+b=c\\
\label{test2}
x*y=z
\end{align}

% ---参考にしたい場所----
\ref{test1}\eqref{tset2}を用いています。
% -----下のように表示されます。↓
% 1は(2)を用いています。

数式環境に\begin{align}を用いましたが、番号付きディスプレイモードの数式であればなんでも大丈夫です。 まず、\label{}の中になにを参考にしているのかわかりやすい文字を入れましょう。

文章には表示されないため自分がわかりやすい名前にするのがよいでしょう。

おまけ

改行後の字下げを消す

Latexでは2回改行すると強制的に字下げが入ります。しかし、字下げをしたくないときは\noindentを使います。

下が字下げされない

\noindent
字下げされてない!!

空白の行を入れる

Latexでは2回改行をしても空白の行を入れることができません。その際に\vskip\baselineskipを使います。

空白の行が欲しい!!
\vskip\baselineskip
yattaze!

意味は、\vskipをスキップしますです。つまり縦方向に空白を入れる。どのくらいを後述します。

\baselineskipは1つ分の行をという意味になるため、合わせることで一つ分の行を空けて!という意味になります。

# (c言語)変な文字が出力される。

 状況

printfで出力した際に最後の方に変な文字(,�,p���U)が出力された。

原因

printf("%s")で終わりが見つからず領域外の文字まで出力している。

<詳細>

printf("%s")は'\0'を見つけたら終わるという仕組みになっているが、配列を作る際にchar a[10]のようにすると、aの領域を確保しただけで、'\0'を最後に入れるといった処理はしていない(char a="abcd"とやった際にはdの後ろに'\0'が含まれる。)。そのため、a[10]の状態でprintf("%s",a)をすると、終わりの記号が見当たらないので、領域外の文字も出力してしまう。

<いつ終わるのか>

ここで疑問になるかもしれないが、じゃあいつ終わるのかという点である。 領域外の文字を出力している際に領域外にたまたま'\0'が存在すると「あ、終わりあったわ」という感じで終われる。この際に'\0'を見つけるまでに変な文字があるとその変な文字を出力してしまうということである。

解決策

print("%s",a)で出力する配列aの最後に'\0'を追加す

(Rails) ダウングレードする7.0.3.1 -> 6.1.6.1

 Railsでダウングレードする。

状況

諸事情によりrailsのバージョンを7.0.3.1から6.1.6.1に変更する必要があった。(6.1.4を入れたかったが、6.1.4を指定しても7.0.3.1が入ってしまった。)

各アプリケーションに移動する。

cdコマンドで各アプリケーションに移動してください。

Railsのバージョンを確認する。

$rails -v #railsのversionを確認するコマンド。
rails 7.0.3.1

Rails,Railtiesをアンインストールする

$gem uninstall rails  #railsをuninstallするコマンド。
Successfully uninstalled rails-7.0.3.1
$gem uninstall railties #railtiesをuninstallするコマンド。
Successfully uninstalled railties-7.0.3.1
#----#
#この時にほかのralisのバージョンが入っていると、以下のようになる。
Select gem to uninstall:
 1. rails-6.1.4
 2. rails-6.1.6.1
 3. rails-7.0.3.1
 4. All versions
> 3 #4でもOK 
#uninstallするバージョンを選んでくださいということ。

Rails 6.1.6.1を入れなおす

gem install rails -v 6.1.6.1 ##railsをinstallするコマンド

Gemfileの更新

今の状態ではGemfileに7.0.3.1を入れるように記述があるのでそこを変更する。

Gemfile

gem 'rails', '~> 7.0.3', '>= 7.0.3.1' #変更前
gem 'rails', '~> 6.1.6', '>= 6.1.6.1' #変更後

bundleを更新する。

$bundle update #ここでエラーがでると思われる。
Bundler could not find compatible versions for gem "actionpack":
In snapshot (Gemfile.lock): actionpack (>= 7.0.3.1)
In Gemfile: rails (~> 6.1.4) was resolved to 6.1.4,
which depends on actionpack (= 6.1.4) importmap-rails was resolved to 1.1.5,
which depends on railties (>= 6.0.0) was resolved to 7.0.3.1,
which depends on actionpack (= 7.0.3.1) 
Running `bundle update` will rebuild your snapshot from scratch,
using only the gems in your Gemfile, which may resolve the conflict.

actionpackが7.0.3.1と6.1.4どちらの方で対応すればよいのかがわかっていない状態。なので、一つ一つ変えてあげる。

$bundle update --conservative actionpack #またエラーが出た。
Bundler could not find compatible versions for gem "activesupport":
.
.
.

同じような感じで今回はactivesupportなので追記する。

$bundle update --conservative actionpack activesupport #エラーがなくなるまで繰り返す

 bundle update ができたらバージョンを確認する。

$rails -v
rails 6.1.6.1

これで入ったので完了。

参考文献

bundle update --conservative で狙った gem だけをアップデートする - ユユユユユ

Rails バージョンアップ時の bundle update におけるバージョンコンフリクト解消方法について

(Rails) エラーを読んでみよう

rails のエラーについて

railsで何かプログラムを作っていると、様々なエラーに直面すると思います。 そこでエラーの意味と怪しい場所について説明します。

今回説明する怪しい部分、対策法は一例ですので、必ずにもその部分を直した結果エラーが改善するとは限りません。ご了承ください。

NoMethodError

これは、method(機能)がうまく使えないときにでるエラーです。

undefined method `title' for nil:NilClass

意味:nil:NilClass の未定義のメソッド `title'(Google翻訳より)

意訳:titleというものは*nilには対応していない

*nil:空白クラスと呼ばれるもの,未定義の時にこのクラスに分類される,何も入っていないと思っても大丈夫

対策:titleが間違っているか、titleの前の@bookが未定義となっていると思われるため、Controllerにて定義がされていないと思われるので、Controllerでしっかりと、@bookが定義されているかを確認する。

詳細:@bookの情報がtitleに流れるが、その際@bookには何も入っていないため、titleは「何もない情報」を受け取ることになる。この時、titleは何もない情報を受け取れない(対応していない)のでエラーが発生する。

NameError

定義されていない変数(variable)やメソッド(method)が使われている。

undefined local variable or method `back' for #<ActionView::Base:0x007f8cbcb56878>

意味:#<ActionView::Base:0x007f8cbcb56878> の未定義のローカル変数またはメソッド「back」(Google翻訳より)

意訳:backというものを私(rails)は知らないよー

対策:backというものを定義するetc...

詳細:今回は、link_toでbackという文字そのものを表示させたかったため、"back"にして、<%=link_to "back", books_path%>という形にした。

Routing Error

urlで検索したページがroutesに記述されていない

No route matches [GET] "/index"

意味:[GET] "/index" に一致するルートはありません(Google翻訳より)

意訳:urlで検索したページがroutesに記述されていない

対策: urlのスペルミス,routesへの書き込み不足を疑う。

詳細:/indexではなく、/booksだったので、urlの部分を/indexに直した。

ArgumentError

0個渡して、2個受け取ろうとしているので、数がおかしい。

wrong number of arguments (given 0, expected 2)

意味:引数の数が間違っています (指定された 0、予想される 2)(Google翻訳より)

意訳:受け取り口が2つあって、0個渡しているからちゃんと2個渡してね

対策: def test(width,height)の部分を削除する,testを呼び出している部分に(100,100)等で2個渡して挙げるようにする。

詳細:今回は@book.testの方に(100,10)と追加させた。

まとめ

このように基本的にエラーに関しては英語で書いてあり、慣れていくとエラーの意味が分かるので、エラーが出た際に一度読む癖をつけるのがおすすめです。

(c言語)2次元配列を作る(malloc対応)

状況

2次元配列の仕組みの振り返り

素数nに対してmallocをどのようにしたらできるのかの備忘録

1次元配列

void main(){
  int i;
  int a[2]={1,2};
  for(i=0;i<2;i++){
    printf("a[%d]=%d\n",i,a[i]); 
  }  
}
a[0]=1
a[1]=2

2次元配列

int i,j;
  int a[2][3]={{1,2,3},{4,5,6}};//2×3の配列
  for(i=0;i<2;i++){//行
    for(j=0;j<3;j++){//列
      printf("a[%d][%d]=%d\n",i,j,a[i][j]); 
    }
  }
a[0][0]=1
a[0][1]=2
a[0][2]=3
a[1][0]=4
a[1][1]=5
a[1][2]=6

少し表示を変えると,

1,2,3
4,5,6

つまり、書く時の書き方を変えてみる。

  int a[3][3]={{1,2,3}
              ,{4,5,6}};

少し見やすくなったかな?、、

ここまででわかったこと

a[i][j] iは行を示すjは列を示す。

mallocによる任意の2次元配列

やり方は2通り 1. 1回のmallocですべての領域を確保する 2. 2回mallocする

イメージは以下のとおりである。

それではコードを見てみよう。

void main(){
  int *a;
  int i,j;
  //やり方1//
  printf("--method[1]--\n");
  a=(int*)malloc(sizeof(int)*(2*2));//2*2の配列を作る
  for(i=0;i<4;i++){
    a[i]=i;
  }
  for(i=0;i<4;i++){
    printf("%d",a[i]);
    if(i==2-1){
      printf("\n");//見やすいように
    }    
  }
  printf("\n");
free(a);
}
  //やり方2//
void main(){
  printf("--method[2]--\n");
  int **aa;
  aa=(int**)malloc(sizeof(int)*2);//行2の配列
  for(i=0;i<2;i++){
    aa[i]=(int*)malloc(sizeof(int)*3);//列3の配列
    
  }
  for(i=0;i<2;i++){//初期化(配列に0を代入)
    for(j=0;j<3;j++){
      aa[i][j]=i+j;
    }
  }
  for(i=0;i<2;i++){//行
    for(j=0;j<3;j++){//列
      printf("%d",aa[i][j]);//出力
    }
    if(j!=3-1){//見やすいように
      printf("\n");
    }
  }
free(aa);
}

結果は以下のとおりである。

--method[1]--
01
23
--method[2]--
012
123

まとめ

  • しっかりと行列を考えてあげることで計算ができる。
  • n次元行列に対しては、2のやり方だと、最初にaaをポインタのポインタのポインタの...にする必要があるので不向き
  • よって、1のやり方で、0番目を0のx方向、1番目を0のy方向,2番目を0のz方向...のように組み合わせるのがベストである気がする。

(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ずつ移動していることに注目する

(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を用いたやり方が汎用性は高いと思われる。