質問です。
VC++10、XP (x86) です。
スタックオーバーフローの先行チェックを行っているぽい、chkstk.asm という
ファイルの98行目で止まってしまします。先行チェックぽいので、本当にスタック
オーバーフローが起こるかどうかは、試せないでいます。release モードでは
コンパイル中にヒープを使い果たしてコンパイルできません。
std::map<unsigned int, CHoge>(void) get_hoge(void)
{
std::map<unsigned int, CHoge> result;
#define ADD_HOGE(idx, a, b, c, d, ... ) \
{ \
result.insert(std::make_pair(idx, CHoge(a, b, c, d, ...))); \
}
ADD_HOGE(0, aaa, 1, 9, 2, ...)
ADD_HOGE(1, bbb, 2, 9, 4, ...)
ADD_HOGE(2, ccc, 3, 9, 2, ...)
// 以下3000行程度、ADD_HOGEマクロを使用してinsert
return result;
}
CHogeは、structで、引数の数が非常に多い60個ほどあります。引数の型は
unsigned int, int, bool, std::string, enum を含みます。
ADD_HOGEの行数が3000行程度なんですが、大幅にカットすると、正常動作します。
ということは、struct への引数を渡す部分で起こっているわけじゃなく、マクロ内にて
変数使用等によって、スタックを使用しているっぽいんですが、{ } でくくっても
問題は解決しません。
ヒントなど、なんでもいいので分かる方教えてください。よろしくお願いします。
スタックのサイズは 1 MB です(/STACK で拡張もできますが)。一般に
1.引数は全てスタックに詰まれます。
2.自動変数/クラスは全てスタック上に作成されます。
メモリーには、プログラム実行用を除くと
A.スタック用
B.ヒープ又はフリーストア
しかありません。
A.がいっぱいならB.を使うしかありません。
orz.
× 詰まれます
○ 積まれます
>>仲澤@失業者
返信ありがとうございます。
スタックサイズを変更すると、2Mで失敗、3Mで成功しました。
しかし、今後3000個より多くなるため、抜本的な解決方法が知りたく、質問させて
いただきました。
スタックを使うのは、
- 引数
- return後のアドレス
- 自動変数
あたりだと思うのですが、これ以外に何かあるんでしょうか。もしくは、デバッグ用の
コードが埋め込まれるとか。
ちなみに、呼び出し規約はデフォルトの __cdecl のままです。
スタックはあくまで「それが妥当な場合」に使いましょう。
1.引数60個は異常です。そんなクラスや関数は設計しなおしましょう。
2.ローカル変数を3000個も作るのは通常ありえません。newした
インスタンスを、newした配列に保管しましょう。
>>仲澤@失業者
>スタックはあくまで「それが妥当な場合」に使いましょう。
どこでスタック消費につながっているかが分からないため、質問させて頂きました。
>1.引数60個は異常です。そんなクラスや関数は設計しなおしましょう。
オンラインゲームサーバーの一部のシステムで、正攻法では50個以上は必要なもので、
ちょっと難しいです。また、3000個程度の要素も同様に必要なものです。
>2.ローカル変数を3000個も作るのは通常ありえません。newした
> インスタンスを、newした配列に保管しましょう。
僕の勘違いだと申し訳ないですが、これはローカル変数扱いとなるのでしょうか?
単なる一時変数(右辺値)だと大丈夫だと思ったのですが。特にVC++10だと右辺値参照が
使えますし、今は積極的に値型を用いています。
ポインタにしますと、debugだと正常動作します。しかし、releaseでのコンパイルは
通らないままでした。
(ちなみに、ここで生成したmapは、以降編集せず、要素へのアクセスをポインタで
アクセスさせているため、あらかじめポインタ化するのは妥当です)
あと、一応無理やり回避する方法は見つかっているのですが、なぜこのようなことが
起こるのか興味があります。
>どこでスタック消費につながっているかが分からないため、質問させて頂きました。
既に説明済みですが、引数とローカル変数がスタックを消費します。
>オンラインゲームサーバーの一部のシステムで、正攻法では50個以上は必要なもので、
>ちょっと難しいです。また、3000個程度の要素も同様に必要なものです。
そんなはずはないと思うのですが・・・structもクラスもnewできますよねぇ。
newの戻り値は「対象クラス等のインスタンスのポインタ」で32bitポインタ値
=4Byteが戻ります(コンパイルオプションが32bitの場合)。
例えば
60個のlongを持つstruct Foo60ならば、原理的に
Foo60 foo60;// スタックから240Byteを消費
Foo60 * foo60_Ptr = new Foo60; // スタックを4Byte、フリーストアから240Byte消費
になります。
以上の説明が理解できない場合は、C++言語の入門書をあたってみてください。
あと、ローカル変数を返却するのはまずいのでやめましょう。
これって、ワーニングされてませんか?。
右辺値参照はムーブセマンティクスと対で用いた時に効果がある話だと
思っているので今回のケースでローカル変数になら無いという話に
繋がるのか疑問です。
この辺の右辺値参照に関しては結構理解するのが難しいという話に
なっていますし、もう一度、右辺値参照の中身について確認された方が
良いかもしれません。
ちなみにめっちゃメンバーの多い変数を3000個もローカルで作るのは
駄目でしょと言う話に関しては私も同感ですね。
後、気になっているのがローカル変数のスコープと寿命って一致してましたっけ?
と言う話です。
何処で読んだのか忘れたのですが、一致していないという話を読んだ事があったので。
VC++2010でどうなっているのかまで確認していないのですけれど。
>>仲澤@失業者
>>どこでスタック消費につながっているかが分からないため、質問させて頂きました。
>
>既に説明済みですが、引数とローカル変数がスタックを消費します。
引数は一時的にスタックを消費し(__cdecl、その他の呼び出し規約により)、ローカル
変数は、関数の呼び出し時に、あらかじめスタックを確保するのが、一般的な
コンパイラが吐くコードだと理解しています。
上のコードだと、ローカル変数は result のみ使用しており、いうまでもなく、
mapの要素はヒープ内に保存されますし、実際
sizeof(std::map<unsigned int, CHoge>) == 20
となりますので、おそらく4個程度のスタックを消費しているかと思います。
ここで、仮に push_back の際に使用されるスタック数を60個とすると、
この関数内で使用されるスタック数は、最大で64個(+α)だと僕は解釈しました。
少なくとも、ollydbgにてスタックを覗いたら、push_back の前後で、60個強の個数
分だけ、スタックを一時的に消費していました。
しかしながら、ollydbg にて、確かに関数呼び出し時に、大量のスタックを確保して
いるのも確認しました。スタックを使っている節はあるのですが、このスタックの
使い道はよく分かりませんでした。
何か、根本的な勘違いをしてるんでしょうか?
>あと、ローカル変数を返却するのはまずいのでやめましょう。
>これって、ワーニングされてませんか?。
いや、これは一般的な値返しです。VC++10なので、ムーブセマンティクスとなることを
期待したコードです。
あれ?
ムーブセマンティクスを使う事が、CHogeのローカル変数が出来ない事に
繋がりましたっけ?
何か勘違いしていたのかな。
ムーブセマンティクスはそれには直接関係無いような気がするのですけれど。
>>PATIO
返信ありがとうございます。
>右辺値参照はムーブセマンティクスと対で用いた時に効果がある話だと
>思っているので今回のケースでローカル変数になら無いという話に
>繋がるのか疑問です。
>この辺の右辺値参照に関しては結構理解するのが難しいという話に
>なっていますし、もう一度、右辺値参照の中身について確認された方が
>良いかもしれません。
get_hoge 関数の戻り値として、result を値で返している点なら、今デバッガで確認
し、ムーブセマンティクスにて返していました。
push_back のところは、ちょい面倒なのでまだ確認してませんが、最大でも数倍程度の
一時消費にしかならないと考えています。
>ちなみにめっちゃメンバーの多い変数を3000個もローカルで作るのは
>駄目でしょと言う話に関しては私も同感ですね。
ローカル変数じゃないつもりなんですが。一時変数(右辺値)を含めてでしょうか?
>後、気になっているのがローカル変数のスコープと寿命って一致してましたっけ?
>と言う話です。
>何処で読んだのか忘れたのですが、一致していないという話を読んだ事があったので。
>VC++2010でどうなっているのかまで確認していないのですけれど。
これは確かに何か関係あるかもしれませんね。大変興味があります。
右辺値参照は、ムーブセマンティクスを呼び出すための仕掛けに過ぎ
無いというのが私の認識です。他の参照を分ける事で呼び出すメンバー関数を
切り替える為の仕掛けと言う認識です。
なので、ロカール変数ができるできないとは関係無いと思ったんですが。
>>PATIO
>あれ?
>ムーブセマンティクスを使う事が、CHogeのローカル変数が出来ない事に
>繋がりましたっけ?
>何か勘違いしていたのかな。
>ムーブセマンティクスはそれには直接関係無いような気がするのですけれど。
ちょっと意味がよくわかりませんが、僕も今回の質問には、直接関係しないと考えています。
>>PATIO
>右辺値参照は、ムーブセマンティクスを呼び出すための仕掛けに過ぎ
>無いというのが私の認識です。他の参照を分ける事で呼び出すメンバー関数を
>切り替える為の仕掛けと言う認識です。
>なので、ロカール変数ができるできないとは関係無いと思ったんですが。
返信の順番がおかしくなってすいません。
たぶんあってます。関係ないと思います。
単純なコピーの場合、コピー元のデータも保証する必要がある為に
コピー先の為に複製を作成する必要が出てきますが、
メンバーにポインタ変数がある場合に参照先のデータを複製する必要が
出てきます。これはコピー元のデータとコピー先のデータを別物に
する事で各々のインスタンスを独立させる為です。
ムーブセマンティクスは、一次変数の場合にどうせ破棄されるのだから
一々複製を作るのは無駄と言うところから来ていて右辺参照でコピーを
行う場合にコピー元の複製をせずにそのままコピー先にポインタを
コピーしてコピー元のポインタを無効にする為のコードを実装する
と言う事だと理解しています。
なので一次変数がローカルに出来ないという話では無いと思っていたんですが、
違うんでしょうか。