6

C++ コードを書いているときに、スタックを使用してメモリを格納するのは良い考えだと学びました。

しかし、最近私は問題に遭遇しました:

次のようなコードを含む実験がありました。

void fun(const unsigned int N) {
    float data_1[N*N];
    float data_2[N*N];

    /* Do magic */
}

コードはランダムにセキュメンテーション フォールトでエクスプロイトされましたが、その理由はわかりませんでした。

問題は、スタックに大きすぎるものを保存しようとしていたことであることが判明しました。これを検出する方法はありますか? または、少なくともそれが間違っていることを検出しますか?

4

5 に答える 5

5
float data_1[N*N];
float data_2[N*N];

これらは可変長配列 (VLA) でNあり、定数式ではありません。パラメータのconst-nessNは、それが読み取り専用であることを保証するだけです。N定数式であることをコンパイラに伝えません。

VLA は C99 でのみ許可されています。C の他のバージョン、および C++ のすべてのバージョンでは許可されません。ただし、一部のコンパイラは、コンパイラ拡張機能として VLA を提供します。GCC でコンパイルしている場合は、-pedanticオプションを使用してみてください。許可されていないことが示されます。

プログラムが segfault を与える理由は、おそらく大きな値によるスタックオーバーフローがN * N原因です:


次のように使用することを検討しstd::vectorてください。

#include <vector> 

void fun(const unsigned int N) 
{
  std::vector<float> data_1(N*N);
  std::vector<float> data_2(N*N);

  //your code
}
于 2012-06-18T09:15:29.117 に答える
2

スタックがいっぱいで、まったく移植可能でないことを検出するのは非常に困難です。最大の問題の 1 つは、スタック フレームのサイズが可変であることです (特に、可変長配列を使用する場合、これは、人々が以前に で行っていたことを行うためのより標準的な方法ですalloca())。したがって、数値のような単純なプロキシを使用できませんスタック フレームの。

ほとんど移植可能な最も簡単な方法の 1 つは、スタック上の既知の深さに変数 (おそらく変数へcharのポインターが a になるような型char*) を配置し、そのポイントから変数 (同じの) までの距離を測定することです。 type) を単純なポインター演算によって現在のスタック フレームに格納します。割り当てようとしているスペースの見積もりを追加すると、スタックが爆発しようとしているかどうかを推測できます。これに関する問題は、スタックが成長している方向がわからないことです (いいえ、すべてが同じ方向に成長するわけではありません!)、スタック空間のサイズを計算すること自体がかなり面倒です (システム制限などを試してみてください。さらに、ハッキング率が非常に高いです。

32 ビット Windows でのみ使用されているのを私が見たもう 1 つのトリックは、alloca()十分なスペースを確保し、十分なスペースがない場合に発生するシステム例外を処理することでした。

int have_enough_stack_space(void) {
    int enough_space = 0;

    __try {           /* Yes, that's got a double-underscore. */
        alloca(SOME_VALUE_THAT_MEANS_ENOUGH_SPACE);
        enough_space = 1;
    }  __except (EXCEPTION_EXECUTE_HANDLER) {}

    return enough_space;
}

このコードは非常に移植性が低く (たとえば、64 ビット Windows で動作するとは考えないでください)、古い gcc でビルドするには、代わりに面倒なインライン アセンブラが必要です。構造化された例外処理 (これが使用されます) は、Windows で最も厄介な黒魔術の 1 つです。(そして、コンストラクトreturnの内部からではありません。)__try

于 2012-06-18T10:04:24.780 に答える
1

これを検出する方法はありますか?

いいえ、一般的に。

スタックサイズはプラットフォームに依存します。通常、オペレーティングシステムがスタックのサイズを決定します。したがって、OS(ulimit -sLinuxの場合)をチェックして、プログラムに割り当てられているスタックメモリの量を確認できます。

コンパイラがサポートしている場合はstackavail()、それを確認できます。スタック制限を超えるかどうかわからない状況では、ヒープに割り当てられたメモリを使用することをお勧めします。

于 2012-06-18T09:25:55.317 に答える
1

代わりに malloc などの関数を使用してみてください。要求したサイズのメモリ ブロックが見つからない場合は、明示的に null を返します。

もちろん、その場合は、関数の終了後にこのメモリを解放することを忘れないでください。

また、バイナリを生成するスタック メモリ制限を使用して、コンパイラの設定を確認することもできます。

于 2012-06-18T09:15:56.107 に答える
1

ヒープ メモリの代わりにスタックを使用する方がよいと人々が言う理由の 1 つは、関数の本体を離れると、スタックの上に割り当てられた変数が自動的にポップアウトされるためです。大きなブロックの情報を格納するには、ヒープ メモリや、リンクされたリストやツリーなどの他のデータ構造を使用するのが一般的です。また、スタックに割り当てられるメモリは制限されており、ヒープ スペースに割り当てられるメモリよりもはるかに少なくなります。大きなデータを格納するためにスタックを使用しようとするよりも、メモリの割り当てと解放をより慎重に管理する方がよいと思います。

メモリ割り当てを管理するフレームワークを使用できます。同様に、VDL を使用して、メモリ リークや解放されていないメモリをチェックすることもできます。

于 2012-06-18T09:19:34.427 に答える