Большой 2D массив дает ошибку сегментации - PullRequest
22 голосов
/ 12 мая 2009

Я пишу код на C ++ в Linux, где я объявил несколько 2D-массивов, например:

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

Во время компиляции ошибки нет. Когда я выполняю, он говорит "ошибка сегментации".

Когда я уменьшу размер массива с 5000 до 50, программа работает нормально. Как я могу защитить себя от этой проблемы?

Ответы [ 7 ]

64 голосов
/ 12 мая 2009

Если ваша программа выглядит так ...

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;
}

Второй самый быстрый способ исправить это - убрать объявление из функции:

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

Третий самый быстрый способ исправить это - выделить память в куче:

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;
}

Четвертый самый быстрый способ - разместить их в куче, используя 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;
}

Пятый самый быстрый способ - это разместить их в куче, но использовать шаблоны, чтобы измерения не были так удалены от объектов:

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;
}

Наиболее эффективный способ - выделить двумерные массивы как одномерные массивы, а затем использовать индексную арифметику.

Все вышеперечисленное предполагает, что у вас есть какая-то причина, хорошая или плохая, для создания собственного механизма многомерных массивов. Если у вас нет причин и вы планируете снова использовать многомерные массивы, настоятельно рекомендуем установить библиотеку:

16 голосов
/ 12 мая 2009

Эти массивы находятся в стеке. Стеки довольно ограничены в размерах. Вы, вероятно, столкнулись с ... переполнением стека:)

Если вы хотите избежать этого, вам нужно положить их в бесплатный магазин:

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

Но вам лучше начать использовать обычные контейнеры, которые обернут все это для вас:

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

Плюс: даже если стек соответствует массивам, вам все равно нужно место для функций, чтобы поместить на него свои кадры.

5 голосов
/ 12 мая 2009

Вы можете попробовать и использовать 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]);

Фактически большой кусок памяти будет выделен в куче и автоматически освобожден при необходимости.

3 голосов
/ 12 мая 2009

Ваша декларация должна отображаться на верхнем уровне, вне какой-либо процедуры или метода.

На сегодняшний день самый простой способ диагностировать ошибку segf в коде C или C ++ - это использовать valgrind . Если один из ваших массивов виноват, valgrind точно определит, где и как. Если неисправность кроется в другом месте, это вам тоже скажет.

valgrind может использоваться в любом двоичном файле x86, но даст больше информации, если вы скомпилируете с gcc -g.

2 голосов
/ 12 мая 2009

Одно замечание о постоянном использовании вектора: насколько я понимаю, если вы уходите от конца массива, он просто выделяет больший массив и копирует все, что может создать тонкие и трудные для поиска ошибки, когда вы действительно связываете работать с массивом фиксированного размера. По крайней мере, с реальным массивом вы столкнетесь с ошибкой, если выйдете из конца, упрощая ошибку.

#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;
}
1 голос
/ 12 мая 2009

Похоже, у вас переполнение стека по-честному Spolsky!

Попробуйте скомпилировать вашу программу с опцией gcc -fstack-check. Если ваши массивы слишком велики для размещения в стеке, вы получите исключение StorageError.

Я думаю, что это хорошая ставка, так как 5000 * 500 * 3 двойных (8 байт каждый) составляют около 60 мегабайт - ни на одной платформе не достаточно стека для этого. Вы должны будете разместить свои большие массивы в куче.

0 голосов
/ 12 мая 2009

Другим решением предыдущих будет выполнение

ulimit -s stack_area

для увеличения максимального стека.

...