デバッグの仕方を間違えるとはまるメモリネタ

これをやっていたのは実は昨日なのですが、あまりにもひどい思い違いを自分がしていたのでそれについてのコメントです。

変数メモリが破壊される?

という現象に悩まされていました。というか、今までそんな現象が出たことがないモジュールであり、モジュールがいくつか絡んでいる処理なのですが、

それぞれのモジュールを単一でテストするとエラーが出ない。が、それらを絡ませるとなぜか途中でメモリが破壊される。という物です。

これだけ書くと「いったいなぜ?」と思いたくなると思います。現にこの現象が出たときに自分が思っていたことも同じでしたので。

いくつかデバッグをしているうちにどうやらメモリ破壊が起こるのはSTLのルーチンらしい、ということになり、

デバッグビルド/リリースビルド関係を確認していたのですが、どうもそうでもない。さて、意味不明だぞ・・・と。

でも、ブレークポイントを後ろのルーチンに設定すると前のルーチンでのメモリ破壊によるエラーは出ない

ブレークをかけないようにするとステップ実行でメモリ破壊が起きていた部分ではメモリ破壊エラーが起きずに正常に実行できる。

やっぱり謎でした。

気分を変えようと風呂に入って・・・

思い返しているうちに何となく理由が読めてきました。というのも、テストしているプログラムですが、概略的にはこんな感じです。

CItemA *g_itemA;
CItemB *g_itemB;
void CItemAStart(CItemA *item) { item->f(); }
void CItemA::f(void)
{
	//ここに長い処理が入る
	g_itemB->g();
	//ここに長い処理が入る
	g_itemB->g();
	//ここに長い処理が入る
}
void CItemB::g(void)
{
	//ここに長い処理が入る
}
int main(void)
{
	g_itemA = new CItemA; g_itemB = new CItemB;
	uintptr_t handle = _beginthread((void (*)(void *))&CItemAStart,0,g_itemA);
	Sleep(2000);
	delete g_itemB;
	WaitForSingleObject((HANDLE)handle,INFINITE);
	CloseHandle((HANDLE)handle);
	delete g_itemA;
	return 0;
}

・・・何となくわかるでしょうか。

賢明な人であれば何が起こったのかわかると思います。つまり、

  1. スレッドが作成されてそちら側の処理が並列でスタートする
  2. Sleepの2000msが経過してdelete命令によりg_itemBは破棄される
  3. この時点でg_itemBのメモリはヒープに返却する
  4. そんなことを知らずCItemA::fやCItemB::gは呼び出されるので、破棄されたメモリにアクセスしたり、STLでヒープからメモリがとられるとg_itemBの領域のメモリが使用されて壊れたように見える

という現象になります。スレッドが挟まっていたことと、デバッグルーチンだから、とSleepで待ち制御を行っていたために起こった自分で作ってしまったバグでした。

悩み始めてから風呂に入るまでにだいたい2時間~3時間くらいこれに悩んでいたのでただの間抜けですね。

スレッドが絡んでいるとメモリ破壊もこれだけわかりづらい理由で起こる、ということも覚えておく必要があります。

もちろん、これは簡略化するとそれなりの人がわかるように見えるだけで実際のルーチンはさらに複雑(スマートポインタクラスによるdeleteの隠蔽など)なので、

こういう風に見える、という変換もできないとだめですね~。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

この記事のトラックバック用URL