参照という考え方

プログラミングなどというものをやっていると、多かれ少なかれ、この単語が発生します(名前は違うかもしれませんが中身は似たようなモンです)。
ところが、言語によって、これの説明をきちんとやっていたり大雑把にやっていたりとまちまちなため、場合によっては色々と不都合が生じます。特にスクリプト系(PerlとかPHPとか)あたり。初心者が扱うC++もその傾向が強いですね。
ちと、メモリへのイメージを持ったりしないといけない分大変ではあるのですが、初心者の域を越えるには恐らく重要な知識でしょう。っな訳で、少々解説などを試みてみたいと思います。
なお、ターゲット層を「少なくともとりあえず一つの言語でなんとなくプログラミングが組めるかも」といったラインにおいておりますので、場合によっては用語に注釈がない場合があります。あらかじめご了承のほどを。

変数ってなに?

大抵のプログラミング言語の学習で、しょっぱなに出てくる「変数」の概念です。まぁ、非常に便利とか何とか言う前に、変数がないとプログラムはまず大抵成り立たないものですが。
この変数を、もう少し噛み砕いて、考えていきます。
そうそう、先に一言。コンピュータが持っているのは「CPU(心臓)」と「メモリ(血液)」だけです。この場合、HDDだろうがMOだろうがCD-ROMだろうが、とりあえずメモリと解釈しておきます。とりあえず、ですが。

で。

変数もまたコンピュータの資源である以上、どこかに存在するわけです。まぁ平たく言ってメモリなのですが。
以下の変数を宣言したとしましょう。色々あるのは(っても二つだけですが)、色々な言語を想定しているためです。
int i = 0;
my ($i) = 0;
いずれも、「i」という名前の変数を宣言しています。よく初心者系の本には「iという名前の箱を用意する」とあります。つまり、「iという名前に結び付けられた箱がメモリ上に用意される」訳です。
これをこのまま解説していくとそのうち混乱するので、簡単に絵(TABLEエレメントじゃん、とかいう突っ込みは無視する方向で一つ)であらわしてみましょう。なお、えらく小さなメモリイメージになっておりますが、なんていうか「気にするな」って感じでお願いできれば、と。
なお、先ほどのiの格納場所を「0x0500h」としておきます。
0x0000H
メモリ番地変数の内容
0x0500H0
0xFFFFH
劇劇に省略された絵ですが、なんとなくイメージはつかめますか?
つまり、iという名前に結びついた箱(変数領域)が0x0500hにあり、そこの中に、iの値が格納されています。これが変数ってモンです。
ちなみに、格納場所は大抵1バイト(8ビット)のサイズを持ちます。勘のいい人は「んじゃぁintとか格納できないジャン」とか思うかもしれませんが、そこは「きっとcharなんだ」ということで理解してください。

関数の引数は…をや?

んでわ、お次は関数に、変数を引数として与えてみましょう。引数1個で、先ほどのiを渡してやります。
これは経験のある人も多いと思うのですが、関数先で引数のiに手を加えても、呼び元のiの値は変化しません。なぜでしょ?
それを、先ほどのメモリイメージで、まず説明してみたいかと思います。
0x0000H
メモリ番地変数の内容
0x0500H0:呼び元での変数i
0x0700H0:関数の引数での変数i
0xFFFFH
こんな風になっております。つまり、関数先でどんなに変更を加えても、それは「0x0700Hに対する変更」でしかないんですね。したがって、呼び元である0x0500Hの内容は変わりません。
しかし、これは便利なときもある反面、面倒なときも多々あります。

閑話休題っぽく:マシン語のお話を少し

ちと、古いあたりを一席。
昔々、人間たちはコンピュータへのプログラムに、直接マシン語っちゅーものを使っておりました。
しかし恐ろしいことに、昔のマシン語は、変数がとても限られていたのです。どんくらい限られていたのかというと、「BCペアレジスタ」「DEペアレジスタ」「IXペアレジスタ」「IYペアレジスタ」「アキュムレータ」の5つです(注:極古い方へ。すんません、贅沢な発言をして)。
この中のうち、アキュムレータと呼ばれる場所は計算に使うので、データの格納場所としては利用できません。よって、事実上4つです。でも、プログラムによっては10個も20個も変数を使うなんてざらです。さて、どうしたらよいのでしょう?

マシン語には、面白い機能がありました。通常データを格納する(変数に値を入れる)ばあい、LD命令というのがありまして。
LD BC 0x200
こんな感じでコードを書いておりました。要約すると「BCペアレジスタ(という変数)に0x200という値を代入する」です。
ところが、こんな風なかき方もあったのです。
LD (BC) 0x200
言葉で書くと「BCペアレジスタの内容が示すアドレスに0x200を代入する」という命令です。
わかりにくいと思うので、二つをメモリイメージで書き出してみましょう。
なお、便宜上BCペアレジスタはメモリの0x0100上にあると仮定します。

一番目 LD BC 0x200
0x0000H
メモリ番地変数の内容
0x0100H0x200
0xFFFFH

二番目 LD (BC) 0x200
0x0000H
メモリ番地変数の内容
0x0100H0x350
0x0350H0x200
0xFFFFH

どうでしょ?特に二番目が重要です。雰囲気くらいはなんとなくわかってきましたか?
説明すると、「BCペアレジスタの内容である0x350番地へデータ「0x200」を代入する」という処理を行っております。
これをうまく利用すると、先ほどの「変数領域が限定されているマシン語」でも、用意に大量の変数が用意できるのです。

マシン語での変数の実装例

では、実際に昔使ったテクニックを紐解いてみましょう。
本当は色々と煩雑になるのですが、ここでは概念を学ぶためなので、簡易的にします。変数の条件を以下のようにします。
  1. メモリは0x7000以降なら好きに使える
    大抵、ある程度のメモリ領域は「使うな」とかOSによって定められているのが普通です。
  2. 変数はすべて4バイトサイズとする:普通はこれが可変になるので面倒なのですが。
  3. 変数は最大で10個使用するものとする:普段は、下手すると一桁単位が異なってくるんですが。
今時の言語なら変数の箱を用意するのも簡単ですが、まずはそこから考えなくてはいけません。ここで、先ほどのLDCの技が登場します。
とりあえず、とっとと回答を、メモリイメージで書いて見ましょう。
0x0000H
メモリ番地変数の内容
0x0100H0x7000H
0x0101H0x7004H
0x0102H0x7008H
0x0103H0x700cH
0x0104H0x7010H
0x0105H0x7014H
0x0106H0x7018H
0x0107H0x701cH
0x0108H0x7020H
0x0109H0x7024H
  
0x7000H変数格納エリア0
0x7004H変数格納エリア1
0x7008H変数格納エリア2
0x700cH変数格納エリア3
0x7010H変数格納エリア4
0x7014H変数格納エリア5
0x7018H変数格納エリア6
0x701cH変数格納エリア7
0x7020H変数格納エリア8
0x7024H変数格納エリア9
0xFFFFH
こーやってやることで、ペアレジスタ一つで、複数の変数を扱えます。
ちなみに実際にアクセスするときは、
LDC (BC+1) 変数1の値
LDC (BC+8) 変数8の値
こんな感じで操作してやります。
こういうやり方を、参照、といいます(やっと本題)。んで、参照の元になる値(今回だとBC+1とか)を「参照ポインタ」とか呼びます。

注釈:
上記の例ですが、厳密には「うそ」です。なんでかってぇと、BCペアレジスタの直後にだらだらとデータなぞ、普通はかけません。
厳密にどうやったのかというと、回答から行くと「参照の参照」です。つまり、BCペアレジスタには0x7000の値だけ置いておいて、変数の場所を列挙する部分自体を0x7000移行に書き込むんですね。
少しだけ絵にすると、以下のとおりになります。
0x0000H
メモリ番地変数の内容
0x0100H0x7000H
  
0x7000H0x8000H
0x7001H0x8004H
0x7002H0x8008H
  
0x8000H変数格納エリア0
0x8004H変数格納エリア1
0x8008H変数格納エリア2
0xFFFFH
で、アクセスは以下のような感じになります。
LDC ((BC)+1) 変数1の値
噛み砕くと「「「BCの参照」+1の参照」に変数を代入」です。…ちと混乱しやすいので、わからない人は気にしないでください。

現代に戻って

さて。いにしえの時代に使えた参照が、現代において使えないわけがありません。現在、参照(ないしポインタ)は、例えば以下のような実装がなされています。なお、前に使った変数「i」を引き続き利用しているものとします。
int *j = &i; // C C++など
$j = \$i; # Perlなど
例によって例のごとく、メモリイメージです。
0x0000H
メモリ番地変数の内容
0x0500H0:変数i
0x0550H0x0500:変数j
0xFFFFH
で。ここで以下のようなミスをすると、悲しいことになります。
j += 10;
$j += 10;
0x0000H
メモリ番地変数の内容
0x0500H0:変数i
0x0550H0x050a:変数j:+10された
0xFFFFH
おそらく皆さんがやりたいであろう操作は、以下のように行います。
*j += 10;
$$j += 10;
これで、
0x0000H
メモリ番地変数の内容
0x0500H10:変数i:+10された
0x0550H0x0500:変数j
0xFFFFH
こうなります。

関数に応用

さて。前の章で出てきた「関数に渡した値を弄くっても元の値が変わらない」状況を、この参照を使ってクリアしてみましょう。
簡単に言うと、関数に、変数のないよう直接ではなく、変数の参照ポインタを渡してあげればいいんですね。
この辺をプログラム的に書くと、こんな感じ。
int foo(int *i);
foo(&i);
.....

sub foo{
my (*i) = @_;
&foo(\$i);
.....

んでもって、性懲りもなくイメージ図。
0x0000H
メモリ番地変数の内容
0x0500H10:変数i
0x0550H0x0500:変数iへのポインタ
0x0800H0x0500:関数fooの中における変数i
0xFFFFH
これで、関数内でデータを書き換えれば、きちんともとのほうにも反映されます。目出度いかどうかは設計次第なのでなんとも言いませんが(笑

実践編:でっかい配列やクラスは参照で渡そう

で、なんでこんなことを長々と書いてきたかというと。例えば、以下のようなことを、初心者はまま行います。Cではおきにくいので、例題はC++とPerl。
int foo(string s);
string s(1024); foo(s);

sub foo{
my ($i) = @_;
$i = 1000文字くらい;
&foo($i);

これを実行するとどうなるのか。今まで見てきてわかるとおり、変数iは一回別の場所にコピーされてから関数に渡ります。つまり、ここでいきなりメモリを食う量が2倍に増えるんですね。
これが、例えば数キロバイトのファイルを読み込んで渡す関数で、しかもあっちこっちに渡しまくると。…あっという間に数百キロバイトとかのメモリを食います。そりゃマシンにも負担が行こうってモンです。ましてや、これが「動画の編集」とかで、一つの配列が数十メガとか数百メガとか、下手すりゃギガとかあった日にゃ…。
こんなとき、ちょっと気にして参照渡しにすれば渡すデータは配列の有無にかかわらず通常4バイト(マシンによっても異なりますが、最近のマシンは大抵4バイトです)。ほんのちょっと意識するだけで、とんでもなく消費メモリが変わってきてしまいます。
この辺の「ちょっとした」意識をしなくてすむのは確かに最近の言語の便利なところなのですが(その辺が、Cがもう一つ「最近の言語」ではない理由(笑))、その分、使い方を間違えるととんでもない目にあう罠もたくさん用意されております。

早々に、〆

上記以外にも、参照をきちんと認識すると「参照の配列」とか「「参照の配列」の配列」とか、色々なテクニックが使えたりもします。使うことがいい事かどうかはまた色々ありますが、Perlで構造体などを作りたいときには事実上必須です。
最近は「メモリとかを意識しないですむ」とか言われておりますが、それは「知ってる人間がとりあえず気にしないでおいておける」だけのお話。決して「知らなくていい」訳ではありません。
中級以上を目指すのであれば、結局のところ、こういった知識は少なからず重要なのがまだまだ今の現状です。…というか、メモリが有限である限り、処理速度が有限である限り、恐らくは常に付きまとってくる問題なのではないでしょうか?(メモリが増加した分、使用すべきメモリ量もまた爆発的に増えましたから)
そんな思いを込めて、ざっくりとですが、筆を進めてみました。
皆様の今後の技術向上の一助にでもなれば、筆者望外の幸いです。

戻る 
Copyright 2003 M-Fr Net All Right Reserved
E-Mail:info@m-fr.net