-
メモリリーク
Posted on 2月 8th, 2005 はおりん No comments[program] Access Violation
オレのやり方が悪いのか、ベクタ側のバグなのか・・・
ベクタにvoid*、WPARAM、どちらを格納しても、push_backで不正アクセス。
STLをよく分かっておらん、というのもあるんだが・・・
うーん、MFCなら使い慣れてるんだがなぁ・・・
ど~も、イテレータというものになじめん。
まぁ、つまりは高度化されたテンプレートリストだから、イテレータは必要なのだが、なにかにつけてイテレータを要求するので、微妙に使い勝手が悪い。
自分の性にあったクラスでコーティングしてしまえばいいんだが、そのまえに、自分の組んだ部分でないコードでヒープ外にでてしまうんだから気持ち悪い。
ポインタを入れてはいけないんだろうか?
それとも、push_backの前になにかしなきゃならんのだろうか?う~む、わからん・・・
あきらめて本でも買うかなー、STLの。cout < < “あはははははは” << endl;
は便利だしなぁ・・・
そういや以前MFCのCStringも、長いこと使ってるとメモリリーク起こしやがったなぁ・・・
動的確保は難しいよ、うん■ [program] 動的メモリ確保をするときはMFCの方がいい
なにもMFCのCArrayがSTLより優れてるとか、CStringはUnicodeも扱えて便利なんだぞ!とか言っているわけではない。
オレが欲しいのはMFCのメモリ監視機能だ。
MFCの欠点として、まず上げられるのは、その独自性だ。
MFCに切り替えることで、プログラムの構造が大きく変わってしまう。
イヤがるひとも多いだろう。しかし!
慣れてしまえば、楽だし、そんなのはSDKも同じなので、まぁ、所詮は慣れである。
ではそうまでしてオレが欲しがる「メモリ監視」とはなんなのか。■ 動的メモリ確保を行っていて、一番問題となるのは、言うまでもなく確保したメモリの解放し忘れである。
サーバソフトウェアなんかでコレをやったら、一日とかからず物理メモリを食いつぶし、仮想メモリすら浸食していくだろう。
メモリを解放し忘れているのはすぐ分かる。
タスクマネージャを見れば、大量のメモリを食っていることが一目瞭然だからだ。
では、それが分かったプログラマーが次に吐く言葉といえばコレだろう。
「どこだ?」
連続稼働テストなんかをするくらいだ、コードは一通りチェックしているだろう。
大きなメモリを解放し忘れているならすぐ分かるだろう。
そんな大きなメモリを使用している箇所などそう多くはないはずだ。では、問題となるのは1回や2回の解放し忘れ程度では気付かないような、数十バイト、数百バイト程度の、いわゆる一時バッファの解放し忘れだ。
一時バッファなぞそこかしこで確保してるし、ポインタも大量にあって、チェックするには莫大な時間がかかる。
コードが大きくなってくればまさに不可能とも言いたくなるような状態だ。
そこで登場するのがMFCのメモリリーク検出構造体、CMemoryStateとなる。■ [program] CMemoryState
コレはいったい何なのか?
コレはクラスではなく、構造体だが、メンバだけでなくメソッドを持っており、クラスとどう違うのか甚だ疑問な構造体である。
この構造体はその名の通り、メモリ状態を保持する構造体だ。
ただし、そのメソッドによって、保持だけでなく、比較、検証まで出来るスグレモノなのだ。■ 実際に使ってみよう
CMemoryStateを利用するには、3つのオブジェクトインスタンスが必要だ。
1つ目は、チェック対象のコードを実行する前のメモリ状態を保存するインスタンス
2つ目は、メモリリークを検証するコード実行後のメモリ状態を取得、保存するインスタンス
3つ目は、上記2つのメモリ状態を比較し、結果を保存するインスタンス
以上3つのインスタンスを生成しておけば、メモリリークのチェックが出来る。実際には、確保した動的メモリが、解放されていなかったときにメモリリークとして検出される。
なお、「確保されていなくて当たり前~」と、自分では思っていても、それが当たり前かどうか、デバッガにはわからないので、それもメモリリークとして検出される。
必要なメモリは全部確保したあとに、検証コード実行前状態を取得するようにしよう。
具体的にはこう使う。#ifdef _DEBUG CMemoryState oldState, newState, chkMem; oldState.Checkpoint(); #endif 検証対象のコード #ifdef _DEBUG newState.Checkpoint(); if(chkMem.Difference(oldState, newState)) { chkMem.DumpStatistics(); } #endif
_DEBUGが定義してあるときだけ実行されるようにすれば、リリース版でパフォーマンス低下が起こるようなことはない。
これでメモリリークがあれば、なんらかのデバッグ表示があるはずだ。
VC++の出力ペインを表示しておくように。
さらに、「__LINE__」を使用して、行数なんかも出力しておくとさらにデバッグしやすいこと請け合いだ。
コレをバッファを使用しているようなところ付近に多数仕掛けておくと、時間とともにメモリを食いつぶすようなプログラムを書く可能性はぐっと低くなるはずだ。Windowsプログラマに一番多いミスはバッファの解放し忘れだと思う。
バッファを至る所に「作らされる」プログラムを書かざるをえないからだ。
この手法を使って、なるべく、メモリをキレイに使って欲しいものである。■ 以上、サルベージ
XPや2000などは、メモリリークを検出し、自動でコレクトしてくれるといわれているが、アレをあまり過信してはいけない。
なぜなら、アレは、プロセス終了時にコレクトしてくれるのであって、プロセスが動き続けている状態ではまったくコレクトしてくれないからだ。
クラスにはデストラクタがあるとはいえ、アレも所詮内部でfreeなどを呼んでいるに過ぎない。
CStringにNULL終端文字以外を入れていると頻繁にメモリリークを起こしてくれるのは、このへんに起因する。ハンドル関連も確か同じだったと思う。プロセス終了時には使用していたハンドルをことごとく解放してくれる(これはどうやらかなり信頼できるようだ。DLLでも使用していない限り、プロセス終了と同時に、ファイルロックもすべて解放された。)が、プロセスを終了しない限り、自力で解放しないと、どんどんとハンドルが増える。
アレはカーネルDLL内にハンドルに関連付けた構造体を作っていることが多いので、思ったよりメモリを消費する。特にBMPハンドルや、DirectX系のハンドルなどは。
過去日記コメントを書く