web-dev-qa-db-ja.com

大きな2Dアレイは、セグメンテーション違反を引き起こします

私はLinuxでいくつかのC++コードを書いています。そこでは、次のようにいくつかの2D配列を宣言しています。

 double x[5000][500], y[5000][500], z[5000][500];

コンパイル中にエラーは発生しません。実行すると「セグメンテーション違反」と表示されます。

配列のサイズを5000から50に減らすと、プログラムは正常に実行されます。どうすればこの問題から身を守ることができますか?

23
kar

プログラムが次のようになっている場合...

int main(int, char **) {
   double x[5000][500],y[5000][500],z[5000][500];
   // ...
   return 0;
}

...次に、スタックがオーバーフローしています。これを修正する最も速い方法は、Wordstaticを追加することです。

int main(int, char **) {
   static double x[5000][500],y[5000][500],z[5000][500];
   // ...
   return 0;
}

これを修正する2番目に速い方法は、宣言を関数の外に移動することです。

double x[5000][500],y[5000][500],z[5000][500];
int main(int, char **) {
   // ...
   return 0;
}

これを修正する3番目に速い方法は、ヒープにメモリを割り当てることです。

int main(int, char **) {
   double **x = new double*[5000];
   double **y = new double*[5000];
   double **z = new double*[5000];
   for (size_t i = 0; i < 5000; i++) {
      x[i] = new double[500];
      y[i] = new double[500];
      z[i] = new double[500];
   }
   // ...
   for (size_t i = 5000; i > 0; ) {
      delete[] z[--i];
      delete[] y[i];
      delete[] x[i];
   }
   delete[] z;
   delete[] y;
   delete[] x;

   return 0;
}

4番目に速い方法は、std :: vectorを使用してそれらをヒープに割り当てることです。ファイル内の行数は少なくなりますが、コンパイルユニット内の行数は多くなります。派生ベクタータイプの意味のある名前を考えるか、グローバル名前空間を汚染しないように匿名の名前空間にそれらを押し込む必要があります。

#include <vector>
using std::vector
namespace { 
  struct Y : public vector<double> { Y() : vector<double>(500) {} };
  struct XY : public vector<Y> { XY() : vector<Y>(5000) {} } ;
}
int main(int, char **) {
  XY x, y, z;
  // ...
  return 0;
}

5番目に速い方法は、それらをヒープに割り当てることですが、テンプレートを使用して、ディメンションがオブジェクトからそれほど離れていないようにします。

include <vector>
using namespace std;
namespace {
  template <size_t N>
  struct Y : public vector<double> { Y() : vector<double>(N) {} };
  template <size_t N1, size_t N2>
  struct XY : public vector< Y<N2> > { XY() : vector< Y<N2> > (N1) {} } ;
}
int main(int, char **) {
  XY<5000,500> x, y, z;
  XY<500,50> mini_x, mini_y, mini_z;
  // ...
  return 0;
}

最もパフォーマンスの高い方法は、2次元配列を1次元配列として割り当ててから、インデックス演算を使用することです。

上記のすべては、独自の多次元配列メカニズムを作成したいという理由があることを前提としています。理由がなく、多次元配列を再度使用する予定の場合は、ライブラリのインストールを強く検討してください。

65

これらのアレイはスタック上にあります。スタックのサイズはかなり制限されています。あなたはおそらく...スタックオーバーフローに遭遇します:)

これを避けたい場合は、無料ストアに置く必要があります。

double* x =new double[5000*5000];

しかし、これらすべてをラップする標準のコンテナーを使用するという良い習慣を始める方がよいでしょう。

std::vector< std::vector<int> > x( std::vector<int>(500), 5000 );

さらに、スタックが配列に適合している場合でも、フレームを配置する関数のためのスペースが必要です。

16
xtofl

Boost.Multi_array を試して使用することをお勧めします

typedef boost::multi_array<double, 2> Double2d;
Double2d x(boost::extents[5000][500]);
Double2d y(boost::extents[5000][500]);
Double2d z(boost::extents[5000][500]);

実際の大きなメモリチャンクはヒープに割り当てられ、必要に応じて自動的に割り当てが解除されます。

5
Benoît

宣言は、プロシージャやメソッドの外側のトップレベルに表示される必要があります。

はるかにセグメンテーション違反を診断する最も簡単な方法 CまたはC++コードではse valgrindです。アレイの1つに障害がある場合、valgrindはどこでどのように正確に特定します。障害が他の場所にある場合は、それも通知されます。

valgrindは任意のx86バイナリで使用できますが、gcc -gでコンパイルするとより多くの情報が得られます。

3
Norman Ramsey

常にベクトルを使用することについての1つの予約:私が理解している限り、配列の最後から離れると、より大きな配列が割り当てられ、すべてがコピーされるため、実際に作業をしているときに微妙で見つけにくいエラーが発生する可能性があります固定サイズの配列。少なくとも実際の配列では、最後から離れるとセグメンテーション違反が発生し、エラーを見つけやすくなります。

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char **argv) {

typedef double (*array5k_t)[5000];

array5k_t array5k = calloc(5000, sizeof(double)*5000);

// should generate segfault error
array5k[5000][5001] = 10;

return 0;
}
2

あなたはスポルスキーに正直なスタックオーバーフローを持っているように私には見えます!

Gccの-fstack-checkオプションを使用してプログラムをコンパイルしてみてください。配列が大きすぎてスタックに割り当てることができない場合は、StorageError例外が発生します。

5000 * 500 * 3のdouble(それぞれ8バイト)は約60メガバイトになるので、それは良い賭けだと思います-そのための十分なスタックを備えたプラットフォームはありません。大きな配列をヒープに割り当てる必要があります。

1
Charlie

以前のものに対する別の解決策は、実行することです

ulimit -s stack_area

最大スタックを拡張します。

0
Tom