大数组大小下的分段错误

136

下面的代码在一台2GB内存的机器上运行时会导致分段错误,但在一台4GB内存的机器上可以正常工作。

int main()
{
   int c[1000000];
   cout << "done\n";
   return 0;
}

数组的大小仅为4Mb。在C++中,是否存在可用于数组大小的限制?

7个回答

154

你很可能会在这里遇到堆栈溢出。数组太大而无法适应程序的堆栈区域;在大多数主流桌面/服务器操作系统上,堆栈增长限制通常为8 MiB或1 MiB(用于用户空间代码)。 (正常的C ++实现使用asm堆栈来进行自动存储,即非 static 局部变量数组。当函数返回或异常通过它们传播时,这使得回收它们免费发生。)

如果你动态分配数组,如果你的计算机有足够的内存,你应该没有问题。

int* array = new int[1000000];    // may throw std::bad_alloc

请记住,即使您的函数通过异常退出,这也需要您手动delete[]数组以避免内存泄漏。在现代C++中强烈不建议使用手动的new/delete,而应该使用RAII


更好的解决方法是使用std::vector<int> array(cppreference)。如果您知道它将增长多大,可以为1000000个元素预留空间。或者甚至可以resize它以默认构造它们(即对内存进行零初始化,与当您声明没有初始值器的普通C风格数组不同),例如std::vector<int> array(1000000)

std::vector对象超出作用域时,其析构函数将为您释放存储空间,即使它是通过父函数捕获子函数的异常退出的。


4
谢谢您的提问。数组为什么要分配在栈上而不是主程序内存中,这是因为栈空间是用来存储函数局部变量的,当函数执行完毕后,栈空间就被自动回收了。相比之下,主程序内存通常用于存储全局变量和动态分配的内存,这些内存需要手动释放,否则会导致内存泄漏。所以在函数中创建数组,可以方便地控制数组的生命周期,避免内存泄漏并提高代码的可读性。 - Mayank
23
该代码使用编译时指定的数组常量分配栈内存。只有通过malloc、new等方式分配堆内存才能存放值。 - Seth Johnson
7
所有自动变量都分配在栈上。如果您查看反汇编代码,您会看到将本地变量的大小从栈指针中减去。当您调用malloc、calloc或任何内存函数时,这些函数会查找足够大以满足您请求的内存块。 - rerun
@Charles,为什么我们可以从堆中分配更多的内存,而不是从栈中分配呢?据我所知,栈和堆在内存中的分配地址空间中是相反方向移动的。 - saurabh agarwal
2
堆不会移动,它甚至不是一个连续的内存区域。分配器只是返回适合您大小要求的空闲内存块。栈和堆在哪里? @saurabhagarwal - phuclv
@Charles,还有一个问题:我重现了这个问题,但发现这实际上是一个运行时错误,如果只分配数组而不写入任何内容,代码就可以正常运行。唯一出现时间错误的情况似乎是在初始化它们的值时。你对此有什么看法? - dragonxlwang

63

在 C 或 C++ 中,局部对象通常分配在堆栈上。由于你正在在堆栈上分配一个大数组,超出了堆栈的处理能力,因此会导致 stackoverflow

不要在堆栈上分配,使用其他地方代替。这可以通过使对象成为全局变量或将其分配到全局中来实现。如果您不从任何其他编译单元中使用全局变量,则全局变量是可以接受的。为确保意外情况下不会发生这种情况,请添加 static 存储说明符,否则只需使用堆即可。

这将在 BSS 段中分配,该段是堆的一部分。由于它在静态存储中,如果不指定其他方式,则会进行零初始化,而不像局部变量(自动存储)包括数组。

static int c[1000000];
int main()
{
   cout << "done\n";
   return 0;
}

非零初始化器会让编译器在DATA段分配空间,这也是堆的一部分。数组初始化器中的所有数据(包括所有隐式的末尾零)都将占用可执行文件中的空间,而不仅仅是大小为零的BSS段。

int c[1000000] = {1, 2, 3};
int main()
{
   cout << "done\n";
   return 0;
}

这将在堆中的某个未指定位置进行分配:

int main()
{
   int* c = new int[1000000];  // size can be a variable, unlike with static storage
   cout << "done\n";
   delete[] c;            // dynamic storage needs manual freeing
   return 0;
}

1
如果您使用第三种模式,在堆上分配内存,请不要忘记在某个阶段删除指针,否则会造成内存泄漏。或者考虑使用智能指针。 - davidA
10
当然,在使用new分配内存时,随处使用delete是一个好的习惯。但如果你确定只在一个地方(比如在主函数中)分配内存,那么严格来说并不需要使用delete - 内存会在程序退出主函数时被自动释放,即使没有显式使用delete - Gunther Piez
'at'drhirsch(你怎么打出@符号的?)- 是的,说得好。由于原帖作者似乎对这门语言不太熟悉,我只是想确保他们和其他看到你好答案的人都意识到第三个选项在一般情况下使用时的影响。 - davidA

22

此外,如果您在大多数UNIX和Linux系统上运行,可以通过以下命令临时增加堆栈大小:

ulimit -s unlimited

但是要小心,记忆是有限的资源,伴随着强大的力量而来的是伟大的责任 :)


2
这是解决方案,但我建议在删除程序堆栈大小的默认限制时要非常谨慎。您不仅会遇到严重的性能下降,而且系统可能会崩溃。例如,我尝试在一台具有4GB RAM的机器上使用快速排序对包含1600万个整数元素的数组进行排序,结果我的系统几乎被杀死了。哈哈 - rbaleksandar
1
@rbaleksandar,我认为你的16MB程序几乎让你的机器崩溃了,因为你正在使用多个数组副本(也许是每个函数调用一个?)尝试更加内存感知的实现吧 ;) - RSFalcon7
我非常确定数组处理没问题,因为我是通过引用而不是值传递。冒泡排序也是同样的情况。该死的,即使我的快速排序的实现很糟糕,冒泡排序也是你不可能实现错误的。哈哈 - rbaleksandar
你可以尝试使用基数排序,或者直接使用std::sort :) - RSFalcon7
1
没有机会。这是实验任务。 :D - rbaleksandar

3
在这种情况下,您的数组是在堆栈上分配的,请尝试使用alloc分配相同大小的数组。

3

因为你把数组存储在栈中,所以应该将其存储在堆中。请参阅此链接了解堆和栈的概念。


1
你的普通数组是分配在栈上的,而栈大小有限,因此你的程序会发生堆栈溢出并崩溃。
最好使用基于std::vector的堆分配数组,它可以增长到几乎整个内存的大小,而不是使用普通数组。 在线尝试!
#include <vector>
#include <iostream>

int main() {
   std::vector<int> c(1000000);
   std::cout << "done\n";
   return 0;
}

然后您可以像平常一样访问数组元素 c[i] ,并且/或者获取它的大小 c.size() (int元素的数量)。
如果您想要具有固定维度的多维数组,则可以使用std::vectorstd::array的混合,如下所示: 在线尝试!
#include <vector>
#include <array>
#include <iostream>

int main() {
   std::vector<std::array<std::array<int, 123>, 456>> c(100);
   std::cout << "done\n";
   return 0;
}

在上面的示例中,您几乎可以获得与分配普通数组int c [100] [456] [123];相同的行为(除了向量在堆上分配而不是堆栈),您可以像在普通数组中一样访问元素c [10] [20] [30]。上面的示例还在堆上分配数组,这意味着您可以拥有整个内存大小的数组大小,而不受堆栈大小的限制。
要获取指向向量中第一个元素的指针,您可以使用&c [0]或只需c.data()

0

还有一种方法对我很有效!你可以通过更改数组的数据类型来减小其大小:

    int main()
        {
        short c[1000000];
        cout << "done\n";
        return 0;
        }

或者

  int main() 
  {
      unsigned short c[1000000];
      cout << "done\n";
      return 0;
  }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接