ポインタであるがゆえのエラー

 ではC++以外の言語に目を向けてみよう。まずCだが,もちろんmallocで確保したメモリを解放し忘れるとメモリ・リークになる。だがCの場合はむしろ,メモリ領域の境界に関するエラーや,ポインタの初期化ミスの方が問題となることが多かった注2)。

 いちばんありがちなのが,ポインタ変数操作の誤りによる他の変数領域の破壊である(リスト3[拡大表示])。配列を操作するときに,その添え字の与え方を間違えればそれだけで他の変数の値を書き換えてしまう。C言語の場合,ポインタは物理的にアドレスを指し示している(図2[拡大表示])。だから関数のアドレスをポインタに入れることもできれば,変数の実体を指すアドレスをポインタに入れることもできる。Cのポインタを理解するには,このメモリ・マップを頭に入れてしまった方がいいだろう。

リスト3●Cでは簡単に他の変数領域を書き換えられる。
ポインタがコンピュータの論理アドレスそのものであり,しかもポインタに対する演算が認められているためである
 
図2●ポインタが指し示すもの。
ここではaという変数に,「program」という文字列の先頭のアドレスが入っている。したがってその値に8を加えたら,この文字列とは関係のないデータまたはプログラムが入っている場所を指し示すことになる

 ポインタを使いたくなる一つの理由は,参照渡しである。Cは基本的に引数の値をコピーして関数を呼び出す(値渡しという)。関数の戻り値は基本的に一つ。したがって複数の値を結果として受け取りたい場合には,ポインタを渡すことによって参照渡しにする(図3[拡大表示])。ポインタを受け取れば,そのアドレスを参照してデータを書き換えることができるので,関数を呼び出した元に結果を返すことができる。

 もう一つポインタの利用を強制されるのは,配列の取り扱いである。文字列も配列の一つで,文字型データの配列という形で実現している。配列データを確保すると,その先頭アドレスを格納したポインタが作られる。

 Cの場合,何が問題かというとポインタを直接演算できることだ(図4[拡大表示])。逆にこのことをうまく利用すれば,メモリを直接操作する必要があるプログラム(デバイス・ドライバや割り込みハンドラなど)をアセンブラより簡単に記述できる。開発効率もアセンブラに比べ圧倒的に高い。

図3●参照渡しで複数の変数を書き換える。
C/C++は基本的に関数を呼び出すとき,引数として渡されるのは値そのものである。関数から戻せる値は一つ。したがって複数個の値を同時に変える処理を記述する場合には,参照渡しをして変数を書き換えるようにする必要がある
 
図4●ポインタによる演算の例。
例えば,ポインタの値を1増やすといった操作が可能だ。ただしここでいう「1」は論理的なもので,型によって実際に指し示すアドレスは変わる

 モダンな言語,JavaやC#には基本的にポインタはない。JavaやC#においては,オブジェクトはすべてC++でいう参照型に相当するものである。実はこのことが多少一貫性を阻害している面がある。JavaとC#はいずれも,メソッドを呼び出すときの引数の取り扱いが値渡しである。にもかかわらず,オブジェクトに関しては参照渡しになってしまう。ここを注意しておかないと,思わぬ副作用を作り込んでしまう可能性がある。

 もう一つ,頻繁に発生するメモリ関連のエラーとして,「Null Pointer Assignment」がある。これはポインタを初期化し忘れたことによって発生するのが一般的である。プログラム終了時にこのメッセージをランタイム・モジュールが表示する。正しいアドレスに初期化していないと,変数領域をクリアしている状態ではたまたま0(=null値)が入っていることが多い。この状態でポインタが示すアドレスに値を入れて操作すると,nullを示すポインタに代入したことになる。またこういったエラーでは,実行した順番によって元々ポインタに入っている値が変わるので,デバッグ中に予想外の数字が入っていて気が付くこともある。

「どうしてこの前,ポインタの話を教えてくれなかったんですか。わかんないから,逃げたんでしょ」
「別にわからないわけじゃないよ。ちょっと話が長くなって面倒そうだな,と思った
だけだよ」

「わかんないのは,ワタシの方ですよ」
「C/C++をあきらめるってのは,どう?」
「そしたら仕事できないじゃないですかぁ」

(北郷 達郎、八木 玲子)