メモリは漏れる。否、漏れる可能性がある。
メモリリーク(メモリ漏れ)は古くからある典型的で、そして致命的なバグのひとつだ。
メモリリークを起こすもっとも単純なコードのひとつは、次のようなものだ。
int m[5];
m[5] = 1;
C言語、そしてC++言語の配列は、宣言した個数分だけ確保される。ただし、その添字(インデクス)は0〜(個数-1)までだ。つまりint m[5]で宣言される配列はm[0],m[1],m[2],m[3],m[4]の五つである。しかし二行目では確保していない五番目の要素にアクセスを試みている。これは明らかに間違いだ。
このように、本来予期していない不正なメモリアクセスのことを、総じてメモリリークと呼ぶことにする。
メモリリークは様々な要因によって引き起こされる。例えばfreeで解放したメモリにアクセスしてしまったり、ポインタに普通の計算結果を代入してしまったり、などである。NULLポインタアクセスも、立派なメモリリークと言える。
ここまでの議論で、「int m[5]で宣言しておいて、m[5]にアクセスするなんて、初心者の犯すミスだ」と思ってしまうかもしれない。もしそうなら、むしろ初心者はあなただ。このミスをするのは決して初心者とは限らない。時には大ベテランのプログラマであっても、メモリリークが原因であることに気づかず、もしくは気づいてもどこを直して良いのかわからず、何週間もウンウンと唸るハメになるのだ。
断言しても良い。殆どの「いまひとつ原因が掴めないけれども確実におこるバグ」もしくは「原因がわからなかったけど、ソースコードの書き方をちょっと変えたら嘘のように収まったが、開発を進めていく内に再発したようなバグ」はメモリリークが原因である。
最も初歩的なメモリリークは一般保護違反を起こす。自分のプログラムを実行している最中に、一般保護違反の文字を見つけたら、あなたは机に八つ当たりする前に神(たぶんインテルかマイクロソフト)に感謝すべきだ。かの偉大なるOSとCPUは、貴方のプログラムがしてはいけない誤りを犯したことを告げてくれたに過ぎない。
一般保護違反は、実に先ほどのプログラムが少し変形しただけで非常に簡単に起こる。ただし、具体的にどんなふうに変形すると確実に起こるかは断言できないので、あまりエレガントとはいえないけど、たとえば以下のようなプログラムはどうだろう。
int m[5];
int i = 0;
for(;;){
m[i] = 1;
i++;
}
もっとも、このプログラムは単にメモリリークを起こしているだけでなく無限ループにもなっているわけだが、このようなプログラムならほぼ確実に一般保護違反を引き起こすことができるだろう。
一般保護違反とは、そもそもなんの保護に違反しているのかというと、コンピュータ全体でもって共有すべき資源のことなのだ。
この場合は具体的にはメモリである。
Windowsでは、複数のプログラムが常に同時にメモリ空間に存在する。前述のようなコードは、32ビットでアドレッシングされる全メモリ空間に対して、無意味な記号「1」を書き込もうとする。これがもし、まるで保護のないMS-DOS環境であったならば、このプログラムは確実に暴走する。みずからのプログラムやOSまでもを塗り替え、アドレッシング不能な空間にまでアクセスを試みるからだ。
しかし全能なるWindowsはそんなワイルドなプログラムがときたま産まれることは承知している。そのため、全てのプログラムを常に監視下においているわけだ。もっとも、実際にそれを監視しているのはCPUに搭載されたメモリ管理ユニットである。
Windows上の全てのアプリケーションはそれぞれアクセスして良い領域を割り当てられ、全ての領域に対してアクセス権(読み込みだけ可能/読み書き可能/実行のみ可能)を設定し、アクセス権のある領域にしかアクセスはできないようになっている。
それを逸脱したとき、CPUは警笛を鳴らし、Windowsはそれを受けて高らかに「一般保護違反」を宣言するのである。このしくみによってアプリケーション作者はバグの原因がメモリリークにある可能性を知るわけだ。また、そのときは問題なくても、潜在的な問題を内包している可能性が高い。
もっとも、あれほどロコツなプログラムを書く人は、まぁ滅多にいないとして置こう。それにしても、まだメモリリークの危険はつきまとう。次のコードがそれだ。
int m[5];
int i;
m[i] = 1;
なんと、よりシンプルになってしまった。しかし問題は実に明白である。原因はiを初期化せずにアクセスしたところにある。このときiになにがはいっているのかはその時々の状況によってまるで違う。場合によっては安全だし、そうでなければ一般保護違反を引き起こすという、実に厄介な代物だ。
一般保護違反を体験したプログラマがラッキーなのは、バグの原因が少なくとも早期のうちに掴めるからである。しかし、多くの場合、メモリリークは静かに内在しつづける。数ヶ月・・・時には数年の潜伏期間の後、ある日突然、牙をむくのだ。
To be continued
|