大型二维数组给出分段错误

Posted

技术标签:

【中文标题】大型二维数组给出分段错误【英文标题】:Large 2D array gives segmentation fault 【发布时间】:2009-05-12 04:22:49 【问题描述】:

我正在 Linux 中编写一些 C++ 代码,其中我声明了一些二维数组,如下所示:

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

在编译过程中没有错误。当我执行时,它说“分段错误”。

当我将数组的大小从 5000 减少到 50 时,程序运行良好。我该如何保护自己免受这个问题的影响?

【问题讨论】:

您声明 y 两次。我想你想要 x、y 和 z。 经典 *** 问题。:-) 顺便说一句,我们认为双 x[5][5] 等正确但双 x[5000][500] 可疑,这有点令人不安。是极限吗?从某种意义上说,一个有效的答案应该是“别担心,根据 C++ 标准,您的代码是完全正确的。” @Daniel Daranas:一个有效的答案,但没有帮助:-) 您的架构上的堆栈有多大?快,现在! 【参考方案1】:

如果你的程序看起来像这样......

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;

最高效的方式是将二维数组分配为一维数组,然后使用索引算法。

以上所有内容都假设您有某种理由(无论是好是坏)想要制作自己的多维数组机制。如果你没有理由,又希望再次使用多维数组,强烈考虑安装一个库:

一种与 STL 完美搭配的方法是 使用Boost Multidimensional Array。

一种快速的方式是使用Blitz++。

【讨论】:

重新检查条件。您分配了 5000 个 double*,但随后只分配了 500 个(for 循环应迭代到 5000) 不错的一个!我没有考虑静电。可能会注意到这使得函数不可重入!【参考方案2】:

这些数组在堆栈上。堆栈的大小非常有限。您可能会遇到...堆栈溢出:)

如果你想避免这种情况,你需要把它们放在免费商店:

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

但你最好养成使用标准容器的好习惯,它为你包装了所有这些:

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

另外:即使堆栈适合数组,您仍然需要空间来放置函数的框架。

【讨论】:

这应该是:'double * x = new double[ 5000*500 ]' with a single pointer *. (或者更麻烦的真实二维动态分配数组...) 关于始终使用向量的一个保留意见:据我了解,如果您离开数组的末尾,它只会分配一个更大的数组并复制所有可能会产生微妙且难以发现的错误的内容当您真的想使用固定大小的数组时。至少对于一个真正的数组,如果你离开最后,你会出现段错误,从而更容易捕捉到错误。 @dribeas:谢谢。事实上,multidim 数组不是我的最爱:) @robert:使用 push_back 将导致向量在需要的地方增长。但是,如果您通过迭代器或直接访问来使用它,则不会出现增长。 @Robert S. Barnes 如果您编写的代码“从数组末尾走开”,那么无论您是否使用 std::vector,它都会被严重破坏。【参考方案3】:

您可能想尝试使用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]);

实际的大内存块将在堆上分配,并在必要时自动释放。

【讨论】:

【参考方案4】:

您的声明应该出现在顶层,在任何过程或方法之外。

到目前为止,诊断 C 或 C++ 代码中的段错误的最简单方法使用valgrind。如果您的一个阵列出现故障,valgrind 将准确指出在哪里以及如何发生。如果故障出在其他地方,它也会告诉你。

valgrind 可用于任何 x86 二进制文件,但如果您使用 gcc -g 编译会提供更多信息。

【讨论】:

把它放在顶层会导致它使用堆。另一种方法是使用 new 或 malloc(记住在完成后适当释放内存)。 Toplevel 使其全局化(未初始化的数据,从未恢复)。通常当人们谈论堆时,他们的意思是动态分配的。 malloc 的问题是您丢失了 2D 数组表示法... @Norman Ramsey 同意,能够编写 x[i][j] 而不是 x[i*XDIM]+j 对于大多数非痴迷的编码人员来说是一个很大的优势。我怀疑 OP 是物理学家。【参考方案5】:

关于始终使用向量的一个保留意见:据我了解,如果您离开数组的末尾,它只会分配一个更大的数组并复制所有可能会在您真正绑定时产生微妙且难以发现的错误的内容使用固定大小的数组。至少对于一个真正的数组,如果你离开最后,你会出现段错误,从而更容易发现错误。

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

【讨论】:

关于不断增长的向量的要点。不过,那只是在您使用 push_back 时。当您从中索引或迭代边缘时,您将遇到同样的段错误。但是,不能保证会出现段错误:仅当在边缘使用无人区标记时。 为什么不“array5k_t array5k = new double[5000][5000];” 因为我用 C 而不是 C++ 编写了示例 typedef 对于掌握多维数组非常有用。【参考方案6】:

在我看来你有一个诚实的 Spolsky 堆栈溢出!

尝试使用 gcc 的 -fstack-check 选项编译您的程序。如果您的数组太大而无法在堆栈上分配,您将收到 StorageError 异常。

不过,我认为这是一个不错的选择,因为 5000*500*3 双倍(每个 8 字节)达到大约 60 兆 - 没有平台有足够的堆栈。你必须在堆上分配你的大数组。

【讨论】:

当我在 gcc 和 g++ 中使用 -fstack_check 选项时,它会告诉 cc1plus: error: unrecognized command line option "-fstack_check" 尝试使用 -fstack-check 而不是 -fstack_check 双 m_x[500000][500],m_y[500000][500],m_z[500000][500];当我在 main() 中仅使用语句(上图)执行时,它正在执行。 @kar 您的优化级别是多少?我相信gcc会在-O0以上的任何地方去除未使用的变量。仅包含普通旧数据声明的函数应编译为仅返回 - 这意味着它仅在理论上使用大量堆栈,而不是在实践中。 (对不起,下划线而不是破折号。已在答案中修复。)【参考方案7】:

之前的另一个解决方案是执行一个

ulimit -s stack_area

扩展最大堆栈。

【讨论】:

如何使用ulimit -s stack_area

以上是关于大型二维数组给出分段错误的主要内容,如果未能解决你的问题,请参考以下文章

处理二维数组时出现分段错误[关闭]

C - 将结构写入二维数组会导致分段错误

C在函数中传递二维数组会给我分段错误

尝试连接二维数组的元素时出现分段错误

使用动态二维数组时的 C++ 分段错误(核心转储)

Python:将二进制文件解压缩成二维数组并给出长度错误