36

C++ では、スタック オーバーフローは通常、プログラムの回復不能なクラッシュにつながります。非常に堅牢である必要があるプログラムの場合、特にスタックサイズが制限されているため、これは受け入れられない動作です。問題の処理方法に関するいくつかの質問。

  1. 一般的な手法でスタックオーバーフローを防ぐ方法はありますか? (大量のスタックを消費する外部ライブラリの処理などを含む、スケーラブルで堅牢なソリューション)

  2. スタックオーバーフローが発生した場合に対処する方法はありますか? できれば、そのような問題に対処するハンドラーができるまで、スタックは巻き戻されます。

  3. 拡張可能なスタックを持つスレッドを持つ言語があります。C++でそのようなことは可能ですか?

C++ の動作の解決策に関するその他の有益なコメントをいただければ幸いです。

4

6 に答える 6

34

スタック オーバーフローの処理は適切な解決策ではありません。代わりに、プログラムがスタックをオーバーフローしないようにする必要があります。

スタックに大きな変数を割り当てないでください (何が「大きい」かはプログラムによって異なります)。既知の最大深度の後に再帰アルゴリズムが終了することを確認します。再帰アルゴリズムが未知の回数または多数の回数再帰する可能性がある場合は、(独自の動的に割り当てられたスタックを維持することによって) 再帰を自分で管理するか、再帰アルゴリズムを同等の反復アルゴリズムに変換します。

「本当に堅牢」でなければならないプログラムは、「大量のスタックを消費する」サードパーティまたは外部のライブラリを使用しません。


一部のプラットフォームでは、スタック オーバーフローが発生したときにプログラムに通知し、プログラムがエラーを処理できるようにすることに注意してください。たとえば、Windows では例外がスローされます。この例外は C++ 例外ではありませんが、非同期例外です。C++ 例外はステートメントによってのみスローされるのthrowに対し、非同期例外はプログラムの実行中にいつでもスローされる可能性があります。ただし、スタック オーバーフローはいつでも発生する可能性があるため、これは予期されることです。関数呼び出しまたはスタック割り当てがスタックをオーバーフローする可能性があります。

noexcept問題は、スタック オーバーフローにより、例外をスローすることが想定されていないコード (たとえば、マークされた関数やthrow()C++ の関数)からでも非同期例外がスローされる可能性があることです。したがって、この例外を何らかの方法で処理したとしても、プログラムが安全な状態にあることを知る方法はありません。したがって、非同期例外を処理する最善の方法は、まったく処理しないことです(*)。スローされた場合、プログラムにバグが含まれていることを意味します。

他のプラットフォームにもスタック オーバーフロー エラーを「処理」するための同様の方法がある場合がありますが、そのような方法はすべて同じ問題に悩まされる可能性があります。エラーを引き起こさないと予想されるコードがエラーを引き起こす可能性があります。

(*) 非常にまれな例外がいくつかあります。

于 2012-08-27T17:20:47.917 に答える
10

次のような優れたプログラミング手法を使用して、スタック オーバーフローを防ぐことができます。

  1. 再帰には細心の注意を払ってください。コードが 100% 正常かどうかわからない場合は、不適切に作成された再帰 CreateDirectory 関数が原因で SO が発生するのを最近見ました。N 回の再帰呼び出し後に実行を停止する保護変数を追加してください。または、再帰関数を書かないほうがよいでしょう。
  2. スタック上に巨大な配列を作成しないでください。これは、クラス フィールドとしての非常に大きな配列のような非表示の配列である可能性があります。ベクトルを使用する方が常に良いです。
  3. alloca をマクロ定義に入れる場合は特に注意してください。高速メモリ割り当てのために alloca を使用していた for ループに文字列変換マクロを挿入した結果、多数の SO が発生するのを見てきました。
  4. スタック サイズが最適であることを確認してください。これは組み込みプラットフォームではより重要です。スレッドがあまり機能しない場合は、小さいスタックを指定し、それ以外の場合は大きいスタックを使用します。予約は、物理メモリではなく、特定のアドレス範囲のみを使用する必要があることを知っています。

これらは、私が過去数年間に見た中で最も SO の原因です。

SO の自動検出については、いくつかの静的コード分析ツールを見つけることができるはずです。

于 2012-08-27T17:42:21.547 に答える
4

Re: 拡張可能なスタック。次のようなもので、より多くのスタックスペースを確保できます。

#include <iostream>

int main()
{
    int sp=0;

    // you probably want this a lot larger
    int *mystack = new int[64*1024];
    int *top = (mystack + 64*1024);

    // Save SP and set SP to our newly created
    // stack frame
    __asm__ ( 
        "mov %%esp,%%eax; mov %%ebx,%%esp":
        "=a"(sp)
        :"b"(top)
        :
        );
    std::cout << "sp=" << sp << std::endl;

    // call bad code here

    // restore old SP so we can return to OS
    __asm__(
        "mov %%eax,%%esp":
        :
        "a"(sp)
        :);

    std::cout << "Done." << std::endl;

    delete [] mystack;
    return 0;
}

これは gcc のアセンブラ構文です。

于 2012-08-27T18:02:33.347 に答える
2

C++ は強力な言語であり、その力には自分自身を撃つ能力が伴います。スタックオーバーフローが発生したときに検出して修正/中止するための移植可能なメカニズムを認識していません。確かに、そのような検出は実装固有です。たとえば、g++ は-fstack-protector、スタックの使用状況を監視するのに役立ちます。

一般に、最善の策は、大きなスタックベースの変数を積極的に回避し、再帰呼び出しに注意することです。

于 2012-08-27T17:20:48.837 に答える