はかせのラボ

私の頭の中を書いていく雑記ブログです

DirectX スタックオーバーフローの恐怖

あいさつ

スタックオーバーフローにはいつもお世話になっているはかせです。
(サイトのほうね)

今日は機能追加をしようとしたら起こった
スタックオーバーフローのお話です。

スタックオーバーフローって何?

①私がお世話になっているサイトです。
プログラミングに関するQ&Aサイトで大体の問題は漁ればあります。
ja.stackoverflow.com

②今回起こったエラーです。
プログラムにはいくつか使用するメモリ領域があるのですが
そのうちの一つにスタック領域というものがあります。
このスタック領域が不足すると発生するエラーです。

スタック領域ってどういう時に使われるの?

①自動変数

自動変数はintとかfloatとか、
プログラマが明示的にメモリを確保しない変数のことです。

//自動変数例
int hoge = 0;
float moge = 0.0f;
double hogehoge = 0.0;

プログラマが宣言しなくても自動で確保され、
スコープを抜けると自動で解放されます。
(だから自動変数。分かりやすいですね)

newとかmallocを使ってプログラマが明示的に確保したメモリは
スタックではなくヒープという別の領域に確保されます。

//ヒープ確保例
class Hoge;
Hoge* hoge = new Hoge();

char* moge = (char*)malloc(4);

実際はスマートポインタ等を使って解放忘れがないようにすると思うので
あまりこんな書き方はしないと思います。
(メモリリーク怖いしね)

②メソッド呼び出し
メソッドを呼び出したとき呼び出し元のアドレスがスタックに積まれます。
でないとメソッド終了時にどこに戻ればいいかわからないからです。

またこの時呼び出したメソッドで使うローカル変数も積まれます。

つまりメソッドの中でメソッドを呼んでさらにまた呼んで・・・
って繰り返してくと、どんどんスタックに積まれるわけですね。

スタック領域の解放は後入れ先出し。
つまり後に積んだものが終わって解放されないと
最初に積んだものは延々と解放されないわけですね。

では原因は?

私のプログラムは親メソッドが子メソッドを呼んで
子メソッドが孫メソッドを呼んでという階層ツリー状に
処理をしていきます。

さて原因となったコードたちです。

/*
オブジェクトのアクティブ状態が変わった時
メソッドを呼ぶ
mPreEnable = 今までのアクティブ状態
*/
void SetEnable(bool enable) 
{
	mEnable = enable; 
	//falseからtrueになったら
	if(mEnable == true)
	{
		if (mPreEnable == false)
		{
			OnEnable();
			mPreEnable = mEnable;
		}
	}
	//trueからfalseになったら
	if (mEnable == false)
	{
		if (mPreEnable == true)
		{
			OnDisable();
			mPreEnable = mEnable;
		}
	}
}

//アクティブになった時
void DXGameObject::OnEnable()
{
	for (auto itr = mComponentsList.begin(); itr != mComponentsList.end(); ++itr)
	{
		auto pItr = *itr;
		pItr->OnEnable();
	}
}

//非アクティブになった時
void DXGameObject::OnDisable()
{
	for (auto itr = mComponentsList.begin(); itr != mComponentsList.end(); ++itr)
	{
		auto pItr = *itr;
		pItr->OnDisable();
	}
}

/*~長いので関係ないとこ省略~*/
Bullet::OnDisable()
{
    mGameObject->SetEnable(false);
}

おそらく勘のいい方ならお判りいただけたかと思います。



わかりましたか?
実はこのプログラム・・・

無限ループしてますorz
f:id:hakase0274:20181122232656p:plain

この図のようにOnDisableのなかで
SetEnable(false)を呼ぶと延々とループしてしまいます。

何故か?
原因はmPreEnableの更新をOnDisableの後にやっていたから。
ならば順番を変えてみましょう!
f:id:hakase0274:20181122232101p:plain

はい解決しました。
いやー無限ループに気が付くのに時間がかかりましたw

なんてったってエラーがスローされたとこ見てもわからないし
その場所も毎回変わるしで犯人の特定に大分手間取りました。

ただ気づいてしまえば修正は秒でした(処理順変えただけ)
みなさんもスタックオーバーフローが起こったら
無限ループを疑ってみるといいかもしれません。

他にもとてつもなくデカい配列を宣言したり
デカいメソッド作ったりしても起こる可能性があるようです。
スタック領域の量を上げる対策もありますが、
まずは自分のプログラムの見直しから始めましょうね(戒め)

バグフィックスも終わりましたし今回はこの辺でノシ

今回作ったものはgithubに上げました
github.com