ポインタ (プログラミング)
出典: フリー百科事典『ウィキペディア(Wikipedia)』
ポインタとは、あるオブジェクトがなんらかの論理的位置情報でアクセスできるとき、その位置を表現する変数の事をいう。有名な例としてはC/C++でのメモリアドレスを表すポインタが挙げられる。
類似の概念に参照 (reference) があるが、ポインタが演算により無効な位置を作り出せるのに対し、参照では元々存在するオブジェクトを指し示す事しかできない。このため一般には参照の方がより安全な操作であるとされる。ただし、参照先のオブジェクトがいつまで有効な状態であるかはプログラムに依存するので、無効なものを指し得ないというわけではない。(参照透過性)
ポインタには不正な領域を示しうるという問題がある。例えば近年セキュリティ上で問題となっているバッファオーバーランの原因の多くは、ポインタ演算のエラーで起こる不正領域の書き換えによるものである。またオブジェクトそのものへの操作とオブジェクトの位置への操作が混在することはプログラマの混乱を招きやすい。このような問題もあって、新しいプログラミング言語では言語レベルでのポインタ機能は排除される方向にある。
むろんこのような言語でもポインタ概念が存在しないわけではない。例えば配列中にオブジェクトを格納してそれを要素のインデックスで参照するとすれば、やはりこれは「ポインタ概念」と呼べる物であり、配列の要素数を超えた領域をアクセスするときにエラーが起こるだろう。だが配列へのインデックスアクセスをも完全排除してしまえば、その言語は制限が強く、少なくとも単純な動作を簡易に記述できる領域を狭めてしまう。このようにポインタは危険性もあるが、プログラミング上強力なテクニックである。
一方で関数型言語などの発展によりポインタの必要性は今後減少する可能性が考えられる。またデータベース領域では、SQLのように関係式からデータを導き出し情報の位置を抽象化する概念が古くからあり、こちらもパラダイムに影響を与えることが考えられる。
目次 |
[編集] C言語のポインタ
C言語の、特定のメモリ領域を表現するポインタは、最も典型的なポインタの例といえる。
Cにポインタが存在する理由は効率上の問題である。元々UNIXを記述するシステム用言語として開発されたCは、アセンブラに可能な操作のほぼ全てを行える必要があった。そのため特定のメモリ領域への値の直接代入能力を持つなど、プログラミング言語全体ではむしろ異例とも言える強力なポインタ機能を備えている。
Cの実行モデルでは、実行プログラム上の関数コード、データが全て一次元のアドレスに直列配置される。そのためデータはおろか、関数のアドレスを取得し、他の関数にエントリー情報として渡す事も可能である。
またCの関数では引数は値渡しのみで参照渡しをサポートしないが、これはアドレスの数値を取得すれば参照に可能な全てを行えるので、実質的に参照を数値と同一視できるからである。実際に初期のCではアドレス値は整数型互換するものとして扱われていた。これは値と参照を明確に区別するPascalなどとは対照的である。現在でもCはvoid*により任意のメモリ領域にアクセスできる(なおC++では参照渡しもサポートするようになった)。
しかしコード領域をも含むメモリを直接扱えるという事は、言語レベルでは(意図的でないとしても)不正なメモリアクセスを事実上保護できないということを示しており、Cのプログラムにおけるポインタ関連のバグの多さがそれを証明している。
[編集] 実際の例
[編集] 単純なポインタ
- 宣言例(変数 "a" をintへのポインタで定義)
int *a;
- 呼びだし例
*a = 10; scanf("%d", &a);
- トリッキーな例
while(*argv!=NULL) printf("%s\n", *argv++);
配列argvがポインタとしても扱えることを利用している。インクリメントするタイミングが少しでもずれると暴走する上、可読性が下がるので、あまり意味の無い苦労といえる。
[編集] 関数ポインタ
上述の通りCでは関数を指すポインタが作れる。
- 宣言例 (引数double・返値doubleの関数を受け取るポインタ "f" を定義)
double (*f)(double);
- 呼びだし例
f = sqrt; printf("f(16) = %f \n", f(16)); printf("f(25) = %f \n", (*f)(25)); /* 明示のためかこちらの方が使われやすい */
関数ポインタを引数にとって使うことで、処理を外部から組み込むことが可能である。主にイベント処理や、判断を外部に任せて処理を行うときなどに用いられる。無名関数の仕組みがないため後者の用途では少し記述が冗長になるが、C++ではBoostという外部ライブラリを使うことでこれを補完できる。
上の例では直接変数を定義したが、実際には可読性を上げるためtypedefして使うことが多い。
typedef double (*mathfunc)(double); mathfunc f; f = sqrt; /* ... */
(ポインタと実体が混乱する例)
[編集] 参照
参照(さんしょう)とは、ポインタと同様「変数がメモリ上に置かれている場所」と解される場合もあるが、それよりもむしろ「その変数を参照する(=変数の値を操作したり出来る)権限」と解されることが多い。参照の概念そのものはメモリの概念と切り離して考えることが可能である(実装上はポインタと同じであることも多い)。
C++ではオブジェクト(インスタンス)を直接変数に代入することが可能だが、Javaなどではオブジェクトそのものは変数に代入できないという文法になっており、オブジェクトの参照を受け取る変数しか宣言できない。このため、それを宣言したのちにnew演算子(新たなインスタンスを生成する演算子)で実際に生成されたインスタンスを受け取る。
- C++の場合
// MyClassは何らかのクラスであるとする MyClass obj1( "init_str" ); MyClass *obj2 = new MyClass( "init_str" ); // C++のnewは返り値がオブジェクトへのポインタ
- Javaの場合
MyClass obj3; // この時点では、単にMyClassへの参照が定義されたにすぎない // MyClass obj4( "init_str" ); // これは文法エラー MyClass obj5 = new MyClass( "init_str" );