参照 (計算機科学)
参照(さんしょう、英: reference、リファレンス)は、他の場所にあるデータを指している情報を含む小さなオブジェクトであり、それ自身の中に(指している)データ自体を含まない。参照の指す値を取り出すことをデリファレンス (dereference) と呼ぶ(間接参照も参照)。参照は様々なデータ構造を構成する基本要素であり、プログラム内の各部で情報をやり取りするための基本でもある。
なお、C++には、参照型というものがあるが、以下で説明するのはC++のそれではなく、一般概念である。C++の参照については、ポインタ (プログラミング)#参照を参照のこと。
住所を使ったたとえ話
[編集]参照は家の住所に似ている。住所は非常に小さな識別子であり、それが指しているモノにはさらに豊富な情報があるだろう。例えば、その家を見れば色がわかるが、住所だけでは色はわからない。住所は単に家を見つけることを可能にするだけである。しかし、もし家の色が知りたければ、住所さえあれば見つけ出して実際にその家を見ればよい。つまり、住所は家の色を知るための充分な情報源となる。住所から家を探すことは、参照をデリファレンスすることに似ている。
もっと複雑な例として、引越しの度に新しい家の住所を古い家に残しておくとする。誰かが最初の家を訪ねると、置いてある住所から次の家へ次の家へとたどっていき最終的に現在の家にたどり着くことができる。これは参照を使用した単純な線形リストに似ている。
住所の別の利点は、それが実際の家よりもずっと扱いやすいことである。例えば、町内の人々を姓の五十音順に並べたいとしよう。ひとつの方法として、巨大なクレーンを使って町内の家を全部物理的に並べ替える方法がある。もっと簡単な方法としては、町内の人たちの住所のリストを作り、姓の五十音順にそれを並べ替えるという方法がある。参照にも同様の利点がある。データへの参照を操作することによってデータ自体を変更することなく様々なことができるし、場合によってはその方が効率的である。
日常生活は参照の例であふれている。電話番号、電子メールアドレス、URLなどなど。いずれも遠隔にあるリソースを指し、それらへのアクセスを可能とする。
参照の利点
[編集]参照によって、オブジェクトを格納する場所、格納方法、コード内での引き渡しなどの柔軟性が増す。参照によって実際のデータにアクセスできるなら、データ自体を移動させる必要はない。また、複数のコードが参照によってひとつのデータを共有することもできる。
ポインタはオブジェクトのメモリ上のアドレスだけを格納したものである。これは最も基本的で間違えやすい参照だが、最も強力で効率的な参照でもある。スマートポインタは不透明データ構造の一種で、ポインタのように働くが、特定のメソッドを通さないとアクセスできない。
ファイルハンドルはファイルの内容を抽象化する参照である。それはファイルへのロックを要求する際にはファイル自体を指すと同時に、ファイルを読む際にはファイル内の特定の位置を指す。
形式表現
[編集]より一般化すると、参照は、あるデータの一意の検索を可能とする別のデータとみなすことができる。これにはデータベースの主キーや連想配列のキーなども含まれる。データの集合 D について、D から D ∪ {null} への一意に定まる関数が参照の定義となる。ここで null は意味のあるものを指していないデータである。
このような関数の別の表現として、「到達可能性グラフ; reachability graph」と呼ばれる有向グラフがある。ここで、各データは頂点として表され、データ u から データv へのエッジがあるとき、u は v を参照している(グラフ理論)。最大出次数は 1 である。このように参照をグラフとして捉えることはガベージコレクションで到達不可能なオブジェクトからのアクセスを分離するのに有効である。
外部収納と内部収納
[編集]多くのデータ構造の中で、大きく複雑なオブジェクトは小さなオブジェクト群から構成されている。そのようなオブジェクト群の格納方法は以下のように2つに分けられる。
- 内部収納(internal storage)[要出典]では、小さなオブジェクトの内容は大きなオブジェクトの内部に格納されている。
- 外部収納(external storage)[要出典]では、小さなオブジェクトは独自の場所に置かれ、大きなオブジェクトはそれへの参照のみを格納する。
内部収納は、参照のための領域や動的メモリアロケーションのためのメタデータを必要とせず、デリファレンスや小さなオブジェクト用のメモリ確保に要する時間も節約でき、効率的である。内部収納は、同種の大きなオブジェクトをメモリ内に連続して配置することで「参照の局所性」を高める効果もある。しかし、外部収納が好まれる状況も以下のようにさまざま存在する。
- データ構造が再帰的(つまり自身を内包する可能性がある)ならば、内部収納は不可能である。
- 大きなオブジェクトが限られた領域に格納されている場合(例えばスタック)、オーバーラン(オーバーフロー)を防ぐためにその内容の大部分を外部収納にして物理的なサイズを削減する必要があるかもしれない。
- 小さなオブジェクトのサイズが可変である場合、それを内部収納すると大きなオブジェクトを可変サイズとする必要が生じ、効率が悪くなることがある。
- 参照を使うことで仕様変更などに柔軟に対応できる。
一例としてJavaでは、プリミティブ型は内部収納であり、オブジェクト(クラス型)や配列は外部収納である。
言語サポート
[編集]アセンブリ言語では、参照はメモリアドレスや配列のインデックスで表現される。これを使うには注意が必要である。メモリアドレスは何を指しているかわからないし、指しているものの大きさも構造も意味もアドレスからはわからない。そのような情報はロジック自体に組み込む。その結果、間違ったプログラムが参照を間違って解釈してエラーが発生し、プログラマは途方にくれることになる。
最初の不透明参照のひとつとして、LISP言語のconsセルがある。これは単純化すれば他の2個のLISPオブジェクトへの参照から構成されるデータ構造であり、他のconsセルへの参照も持つことが出来る。この構造で単純な線形リストを構成することもできるし、「ドットリスト」と呼ばれる二分木を構成することもできる。
他の初期の言語FORTRANは明示的な参照を持っていないが、参照渡しで暗黙のうちにそれを使っている。
C言語で導入されたポインタは、原始的な参照の形態のひとつである。これはアセンブリ言語の生アドレス (raw address) 表現と似ているが、ポインタの参照しているデータが誤って解釈されることのないよう、コンパイル時に使用される静的なデータ型の概念を導入しているという点で異なる。しかし、C言語は「弱い型システム」を採用していることから、型変換(あるデータ型の値を明示的に他のデータ型の値に変換すること)によって不正なポインタを容易に生成することができるため、誤った解釈は依然として生じうる。Cの後継とも言えるC++は、新たな型変換演算子の導入、標準ライブラリでのスマートポインタ導入などによりポインタの型安全性を強化しようと試みているが、Cとの互換性維持のため意図的にこれらの安全機構を出し抜くことができる能力を依然として有している。なお、C++には、さらに型としても「参照」というものがある(#C++の「参照」を参照)。
ガベージコレクションをサポートするような多くの高水準言語では、reference などと称される不透明な参照を採用している。これらの参照はC言語のポインタのようなデータ型であるが、参照を生のアドレス値に変換したり、逆にアドレス値から参照を生成したり、といった危険な変換ができないという点でC言語よりずっと安全になっている。このように「管理された」言語 (managed language) では、参照は実際には指すべきデータへのポインタへのポインタになっていることが多い。C/C++から見れば、これらの言語は二段階ポインタを参照に使っていることになる(典型的な実装としては)。ガベージコレクタだけが不透明性を生む中間のポインタに直接アクセスすることができる。一般に参照同士の演算もサポートされていない。
FORTRAN
[編集]FORTRANで参照と言えば、オブジェクトの別名 (alias) を意味することが多い。例えばスカラ変数、配列の行と桁などである。参照をデリファレンスする方法はなく、参照されているものを直接操作するという概念もない。FORTRANの参照は null の場合もある。他の言語のように参照によって線形リスト、キュー、木構造などの動的構造を処理することができる。
Java
[編集]Javaのデータ型には、大別して参照型とプリミティブ型が存在する。クラス型、インタフェース型、型変数(ジェネリクスの型引数)、配列型が参照型(reference types)である[1]。
C#
[編集]C#のデータ型には、大別して参照型、値型、ポインタ型が存在する。クラス、インターフェイス、配列、デリゲートは参照型である。数値型や論理型を含む構造体や列挙型は値型である。
関数型言語
[編集]この節の加筆が望まれています。 |
C++の「参照」
[編集]この節の加筆が望まれています。 |