数组类型和使用malloc分配的数组之间的区别

77

今天我在帮助我的一个朋友处理一些C代码,我们遇到了一些奇怪的行为,我无法解释为什么会发生这种情况。我们有一个包含整数列表的TSV文件,每行一个int。 第一行是列表中行数的数量。

我们还有一个非常简单的“readfile” c文件。第一行读取 n,即行数,然后进行初始化:

int list[n]

最后使用fscanf做一个包含n的for循环。对于小的n(直到约100,000),一切都正常。但是我们发现当n很大(10^6)时,会发生段错误。

最终,我们将列表初始化更改为

int *list = malloc(n*sizeof(int))

一切都很顺利,即使处理非常大的n也没有问题。

有人能解释一下为什么会出现这种情况吗?为什么使用int list[n]时会导致段错误,在使用list = malloc(n*sizeof(int))后就停止了呢?


正是我正在寻找的,我在HackerRank数组操作问题中遇到了同样的问题。 - Kapil
9个回答

175

这里涉及到几个不同的部分。

首先是声明数组时的差异,如下所示:

int array[n];

int* array = malloc(n * sizeof(int));
在第一个版本中,你声明了一个具有自动存储期的对象。这意味着数组仅在调用它的函数存在时才存在。在第二个版本中,你使用了动态存储期的内存,这意味着它将存在直到通过free显式释放为止。
第二个版本之所以能够在这里工作是C通常编译方式的实现细节。通常,C内存分为几个区域,包括堆栈(用于函数调用和局部变量)和堆(用于malloc的对象)。堆栈的大小通常比堆小得多;通常是8MB左右。因此,如果尝试分配一个巨大的数组,就可能导致堆栈溢出并使程序崩溃。
int array[n];

如果你使用栈空间存储数据,可能会超出栈的存储空间,导致段错误。另一方面,堆通常具有巨大的大小(例如,与系统上可用的空闲空间一样大),因此使用malloc分配一个大对象不会导致内存不足错误。

总的来说,在C语言中要小心使用变长数组,因为它们很容易超出栈的大小。除非你知道大小很小或者确实只需要短时间使用该数组,否则最好使用malloc


2
好的回答!我在想速度是否也有差别? - Tropilio
1
由于引用局部性的影响,我会怀疑栈分配的数组访问速度更快,而且malloc本身比仅仅移动栈指针要慢得多。但实际上,最好使用适合当前任务的方法。 - templatetypedef
或者,如果你在任何函数之外声明 int arr[1000000];,它们会自动设置为零并存储在堆上。 - DSOI__UNUNOCTIUM
int array[n] 作为顶层声明(在函数外部)是什么意思?这也会放在堆栈上吗? - xjcl
在函数外,对象具有静态存储期。它们通常被放置在与堆栈和堆不同的内存区域中。 - templatetypedef
显示剩余2条评论

16
int list[n]

上分配n个整数的空间,通常栈非常小。使用栈上的内存比另一种选择快得多,但栈非常小,如果您像分配大数组或进行过深的递归等操作,则很容易溢出堆栈(即分配过多的内存)。您不必手动释放通过此方式分配的内存,当数组超出作用域时编译器会自动完成。

另一方面,malloc中分配空间,堆通常比栈要大得多。您需要在堆上分配更大量的内存才能耗尽它,但在堆上分配内存比在栈上慢得多,并且在使用完之后必须通过free手动释放它。


1
“在堆栈上使用内存比使用其他方式分配内存要快得多”,这里的意思是“分配”还是“访问”?据我所知,堆栈分配速度更快,但对于访问(读/写)也是如此吗?谢谢。 - dragonxlwang

3

int list[n] 存储在栈中,而 malloc 存储在堆中。

栈的空间有限,可用空间较小,而堆则要大得多。


1

int list[n] 是一个VLA,它在堆栈上分配内存而不是在堆上。你不需要释放它(它会在函数调用结束时自动释放),并且它分配速度很快,但存储空间非常有限,正如你所发现的那样。你必须在堆上分配更大的值。


1
如果您使用的是Linux系统,可以将ulimit -s设置为更大的值,这可能也适用于堆栈分配。当您在堆栈上分配内存时,该内存会一直保留到函数执行结束。如果您在堆上分配内存(使用malloc),则可以随时释放内存(甚至在函数执行结束之前)。通常,堆应用于大内存分配。

1

这个声明在堆栈上分配内存

    int list[n]

malloc在堆上分配内存。

栈的大小通常比堆小,因此如果在栈上分配太多内存,会导致堆栈溢出。

另请参见此答案以获取更多信息


1

假设您在实施中有一个典型的实现,那么最有可能的是:

int list[n]

在你的堆栈上分配列表,而:

int *list = malloc(n*sizeof(int))

在堆上分配内存。

对于栈来说,它们通常有一个增长的限制(如果它们可以增长的话)。对于堆来说,仍然存在一个限制,但这往往要大得多,并且(广义地)受到您的RAM+swap+地址空间的约束,这通常至少比栈大一个数量级,甚至更多。


0
   int array[n];

这是一个静态分配数组的示例,编译时将知道数组的大小,并且该数组将在堆栈上分配。
   int *array(malloc(sizeof(int)*n);

这是一个动态分配数组的例子,数组大小将在运行时由用户指定。数组将在堆上分配。

0

当您使用malloc进行分配时,内存是从堆中分配的,而不是从栈中分配,后者的大小要小得多。


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