31

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

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

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

配列のサイズを 5000 から 50 に減らすと、プログラムは正常に動作します。この問題から身を守るにはどうすればよいですか?

4

7 に答える 7

76

あなたのプログラムがこのように見える場合...

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

...その後、スタックがオーバーフローしています。これを修正する最も速い方法は、 staticという単語を追加することです。

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 次元配列として割り当ててから、インデックス演算を使用することです。

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

于 2009-05-12T04:47:44.397 に答える
18

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

これを回避したい場合は、それらをフリー ストアに配置する必要があります。

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

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

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

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

于 2009-05-12T04:28:30.840 に答える
5

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]);

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

于 2009-05-12T05:03:50.690 に答える
4

宣言は、プロシージャまたはメソッドの外側の最上位に表示する必要があります。

C または C++ コードでsegfault を診断する最も簡単な方法は、 valgrindを使用することです。アレイの 1 つに障害がある場合、valgrind は正確にどこでどのように発生するかを特定します。障害が他の場所にある場合は、それも教えてくれます。

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

于 2009-05-12T04:27:30.140 に答える
2

常に vector を使用することに関する 1 つの留保: 私が理解している限りでは、配列の末尾から離れると、より大きな配列が割り当てられ、すべてがコピーされるため、実際に作業するときに微妙で見つけにくいエラーが発生する可能性があります。固定サイズの配列。少なくとも実際の配列では、エラーをキャッチしやすくするために端から離れた場合に segfault が発生します。

#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;
}
于 2009-05-12T06:52:06.003 に答える
1

Spolsky に正直なスタック オーバーフローがあるように見えます。

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

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

于 2009-05-12T04:31:08.857 に答える
0

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

ulimit -s stack_area

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

于 2009-05-12T05:03:56.337 に答える