web-dev-qa-db-ja.com

C ++でスタックオーバーフローを処理または回避する方法

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

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

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

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

C++の動作の解決に関するその他の役立つコメントをいただければ幸いです。

29
Ralph Tandetzky

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

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

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


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

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

他のプラットフォームでも、スタックオーバーフローエラーを「処理」するための同様の方法がある場合がありますが、そのような方法では同じ問題が発生する可能性が高くなります。

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

23
James McNellis

次のような適切なプログラミング方法を使用して、スタックオーバーフローから保護できます。

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

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

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

7
marcinj

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

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

2
Mark B

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のアセンブラー構文です。