我正在研究用c编写的模拟问题,我程序的主要部分是递归函数。 当递归深度达到大约500000时,似乎发生堆栈溢出。
Q1 :我想知道这是正常的吗?
Q2 :一般来说有多少递归函数调用导致堆栈溢出?
Q3 :在下面的代码中,删除局部变量neighbor
可以防止堆栈溢出?
我的代码:
/*
* recursive function to form Wolff Cluster(= WC)
*/
void grow_Wolff_cluster(lattic* l, Wolff* wolff, site *seed){
/*a neighbor of site seed*/
site* neighbor;
/*go through all neighbors of seed*/
for (int i = 0 ; i < neighbors ; ++i) {
neighbor = seed->neighbors[i];
/*add to WC according to the Wolff Algorithm*/
if(neighbor->spin == seed->spin && neighbor->WC == -1 && ((double)rand() / RAND_MAX) < add_probability)
{
wolff->Wolff_cluster[wolff->WC_pos] = neighbor;
wolff->WC_pos++; // the number of sites that is added to WC
neighbor->WC = 1; // for avoiding of multiple addition of site
neighbor->X = 0;
///controller_site_added_to_WC();
/*continue growing Wolff cluster(recursion)*/
grow_Wolff_cluster(l, wolff, neighbor);
}
}
}
答案 0 :(得分:4)
我想知道这是正常的吗?
是。堆栈大小只有这么多。
在下面的代码中,删除局部变量neighbor可以防止堆栈溢出?
没有。即使没有变量也没有返回值,函数调用本身必须存储在堆栈中,这样堆栈最终可以解开。
例如......
void recurse() {
recurse();
}
int main (void)
{
recurse();
}
这仍然会溢出堆栈。
$ ./test
ASAN:DEADLYSIGNAL
=================================================================
==94371==ERROR: AddressSanitizer: stack-overflow on address 0x7ffee7f80ff8 (pc 0x00010747ff14 bp 0x7ffee7f81000 sp 0x7ffee7f81000 T0)
#0 0x10747ff13 in recurse (/Users/schwern/tmp/./test+0x100000f13)
SUMMARY: AddressSanitizer: stack-overflow (/Users/schwern/tmp/./test+0x100000f13) in recurse
==94371==ABORTING
Abort trap: 6
一般来说,有多少递归函数调用导致堆栈溢出?
这取决于您的环境和函数调用。在OS X 10.13上,我默认限制为8192K。
$ ulimit -s
8192
这个带clang -g
的简单示例可以递归261976次。使用-O3
我无法让它溢出,我怀疑编译器优化已经消除了我的简单递归。
#include <stdio.h>
void recurse() {
puts("Recurse");
recurse();
}
int main (void)
{
recurse();
}
添加一个整数参数,它是261933次。
#include <stdio.h>
void recurse(int cnt) {
printf("Recurse %d\n", cnt);
recurse(++cnt);
}
int main (void)
{
recurse(1);
}
添加一个双参数,现在是174622次。
#include <stdio.h>
void recurse(int cnt, double foo) {
printf("Recurse %d %f\n", cnt, foo);
recurse(++cnt, foo);
}
int main (void)
{
recurse(1, 2.3);
}
添加一些堆栈变量,它是104773次。
#include <stdio.h>
void recurse(int cnt, double foo) {
double this = 42.0;
double that = 41.0;
double other = 40.0;
double thing = 39.0;
printf("Recurse %d %f %f %f %f %f\n", cnt, foo, this, that, other, thing);
recurse(++cnt, foo);
}
int main (void)
{
recurse(1, 2.3);
}
等等。但是我可以在这个shell中增加我的堆栈大小并获得两次调用。
$ ./test 2> /dev/null | wc -l
174622
$ ulimit -s 16384
$ ./test 2> /dev/null | wc -l
349385
我有一个很大的上限,我可以制作65,532K或64M的叠加量。
$ ulimit -Hs
65532
答案 1 :(得分:1)
是和否 - 如果您在代码中遇到堆栈溢出,则可能意味着一些事情
您的算法的实现方式不是考虑到您给定的堆栈内存量。您可以调整此数量以满足算法的需要。
如果是这种情况,那么更常见的可以更改算法以更有效地利用堆栈,而不是添加更多内存。例如,将递归函数转换为迭代函数可以节省大量宝贵的内存。
尝试吃掉所有内存的错误。你忘记了递归中的基本情况或错误地调用了相同的函数。我们都是在至少 2次完成的。
不一定多少次调用会导致溢出 - 它取决于每个单独调用占用堆栈帧的内存量。每个函数调用都会占用堆栈内存,直到调用返回为止。堆栈内存是静态分配的 - 您无法在运行时(在一个理智的世界中)更改它。它是幕后的后进先出(LIFO)数据结构。
它没有阻止它,它只是改变了溢出堆栈内存所需的grow_Wolff_cluster
次调用次数。在32位系统上,从函数中删除neighbor
会导致调用grow_Wolff_cluster
减少4个字节。当你将其乘以数十万时,它会迅速增加。
我建议您了解有关堆栈如何为您工作的更多信息。 Here's a good resource在软件工程堆栈交换上。另一个在stack overflow(zing!)
答案 2 :(得分:1)
堆栈溢出不是由C标准定义的,而是由实现定义的。 C标准定义了一种具有无限堆栈空间的语言(以及其他资源),但确实有一个关于如何允许实现限制的部分。
通常情况下,操作系统实际上首先会创建错误。操作系统不关心您进行了多少次调用,而是关注堆栈的总大小。堆栈由堆栈帧组成,每个函数调用一个。通常,堆栈帧由以下五个事物的某种组合组成(作为近似值;系统之间的细节可能有很大差异):
++i
循环中for
指令的地址)。因为其中一些(特别是,1,4和5)的大小可能会有很大差异,所以很难估计平均堆栈帧的大小,尽管在这种情况下它更容易因为递归。不同的系统也有不同的堆栈大小;它目前看起来像默认情况下我可以有8 MiB的堆栈,但嵌入式系统可能会少很多。
这也解释了为什么删除局部变量会为您提供更多可用的函数调用;你缩小了500,000个堆栈帧的大小。
如果你想增加可用的堆栈空间,请查看setrlimit(2)
function(在Linux上就像OP一样;在其他系统上可能会有所不同)。首先,您可能希望尝试调试和重构,以确保您需要所有堆栈空间。
答案 3 :(得分:0)
每次函数重复时,程序在堆栈上占用更多内存,每个函数占用的内存取决于函数及其中的变量。可以对函数执行的递归次数完全取决于您的系统。
没有一般的递归数会导致堆栈溢出。
删除变量'neighbor'将允许函数进一步重复,因为每次递归占用的内存较少,但最终仍会导致堆栈溢出。
答案 4 :(得分:0)
这是一个简单的c#函数,它将向您显示计算机在堆栈溢出之前可以进行多少次迭代(作为参考,我已经运行了10478次):
private void button3_Click(object sender, EventArgs e)
{
Int32 lngMax = 0;
StackIt(ref lngMax);
}
private void StackIt(ref Int32 plngMax, Int32 plngStack = 0)
{
if (plngStack > plngMax)
{
plngMax = plngStack;
Console.WriteLine(plngMax.ToString());
}
plngStack++;
StackIt(ref plngMax, plngStack);
}
在这种简单情况下,条件检查:“是否可以删除(plngStack> plngMax)”, 但是如果您拥有真正的递归函数,此检查将帮助您定位问题。