Deprecated: The each() function is deprecated. This message will be suppressed on further calls in /home/zhenxiangba/zhenxiangba.com/public_html/phproxy-improved-master/index.php on line 456
[go: Go Back, main page]

Stop the Leak [I]
11/23 リークを止めろ!(前編)


ゴキブリは一匹みつけたら、その家に最低三十匹はいる計算になるという。果たしてバグはどうだろう?





SHOW STOPPER!

 モノにバグは憑き物だ。バグのないモノを作ることは、完璧な人間がいないのと同じで不可能に近い。従って世にあるほぼすべての製品は、たくさんのバグがあることを承知の上で店頭に並べられている。それを買う人間も、基本的には少なからずのバグがあることを承知で購入する。そう、バグは人類が決して逃れることのできない業なのだ。

 だがしかし、プロの世界では、その大前提のうえでどうしてもこれだけは許せないという致命的なバグがある。そのバグのせいで、高い確率で製品としての価値が台無しになるような、まさに致命的という表現がぴったりくる超特大ホームラン級バグ。それをショウストッパーと呼ぶ。

 ショウストッパーのあるまま出荷される製品は滅多にない。製品出荷前に数千件の未解決のバグ(まさにX-Files)を抱えたまま鳴り物入りで発売されたWindows98であっても、ショウストッパーだけはほぼ全てが取り除かれているはずだ(それだけでも膨大なかずに登る)。

 どんなバグがショウストッパーになるのかというと、例えばOSがフォルダを開いて閉じただけでシステムがハードディスクを初期化してしまったり、ゲームをスタートさせると同時にハングアップしてしまったりするものはもちろんのこと、画面が不用意に乱れたり、「たまに」規定の挙動をしないなど、かなり謎の多いものも含まれる。

 ショウストッパーは最強のバグだ。そのため、発見が容易なときはまだ幸運なほうで、100時間連続で動かすと70%の確率で発生する、といったタイプのものや、画面がときどき乱れる、など、まるで原因がつかめないものなどがある。

 このようなバグを起こしている原因はなんだろうか。無論、その原因は様々だ。しかし実に典型的なショウストッパーは、実は非常に単純な原因から発生する。それはメモリリークである。



漏れるメモリ

 メモリは漏れる。否、漏れる可能性がある。
 メモリリーク(メモリ漏れ)は古くからある典型的で、そして致命的なバグのひとつだ。

 メモリリークを起こすもっとも単純なコードのひとつは、次のようなものだ。
      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