A调用B,其中B调用A,巨大的堆栈跟踪

时间:2014-05-30 02:24:36

标签: c# methods stack-trace

我有一个类有两个这样的方法:

int counter = 0;

private void A ()
{
    Debug.Log (System.Environment.StackTrace);
    counter++;
    B ();
}

private void B ()
{
    counter++;

    if (counter < 100)
        A ();
}

这编译并正常工作,并按预期在100次迭代后停止。但是,我有点担心,到最后,堆栈跟踪变得非常庞大。

我试着寻找这种模式,但实际上找不到任何东西。这样做的事情一般都是禁止的,或者它是否正常,只要它正确终止?

编辑:澄清使用这个:这实际上是我的程序的主要循环,应该永远持续下去;它并不意味着任何类型的递归函数。基本上我有一个大循环,但由于各种原因我将它分成多个较小的方法,并且它们以这种方式相互调用。从功能上讲,它只是在一个巨大的while循环中拥有所有代码,但我担心堆栈跟踪。

4 个答案:

答案 0 :(得分:1)

这是一种递归模式,本身并不是一个糟糕的设计。只要递归调用生成的堆栈(取决于调用次数和每次调用的参数数量)不超过最大堆栈大小(对于.NET应用程序默认为1 MB),您就可以了。

如果确实超过了它,则必须编写(必然存在的)命令式等效项。

注意:

与堆栈大小相比,100个函数调用无效。

答案 1 :(得分:1)

您正在做的是两步递归调用,与扫描目录结构的操作非常类似

伪代码:

Function ScanDirectory CurrentDirectory

    DirectoryList.Add CurrentDirectory

    For each Directory in CurrentDirectory

        ScanDirectory Directory

但是递归函数总是必须得到很好的控制,因为如果递归没有限制,你将得到一个奇妙的StackOverflow异常。

目录扫描非常适合查看这些内容,文件系统不允许超过256个深度,因此您可以确定递归限制为256.

如果你担心可能的溢出,那么你必须实现一些技术来削减调用堆栈,而不是直接调用方法,你可以将它添加到线程池中的工作,这样每次调用将在一个不同的线程中,每次调用时堆栈都是空的(这只是一个非常脏的例子;)

答案 2 :(得分:1)

这是递归 - 一种非常常见的编程技术。不同方法相互调用的经典情况是recursive descent parsing:在那里,当被解析的表达式有很多嵌套时,堆栈也会变得非常深。

在决定递归的可用性时,您应该考虑最坏情况下的最大调用深度,作为输入大小的函数。如果函数以对数方式增长,则递归很可能是正常的。如果它线性增长,并且输入可能有几千个项目,那么递归几乎肯定会因堆栈溢出而崩溃。

一个重要的例外是tail recursion:编译器可能会对其进行优化,让具有线性堆栈要求的算法运行完成。

答案 3 :(得分:0)

每次调用方法时,该方法都会被放入堆栈跟踪中。这样当当前方法返回时,它知道返回的位置。

在您的代码示例中,您正在按其他答案中的说明以递归方式调用方法A() 。如果仔细查看堆栈跟踪,您会发现它看起来像这样:

A() which calls...
B() which calls...
A() which calls...
B() which calls...
A() which calls...
B() which calls...
....

堆栈跟踪大于预期的原因可能是因为每次调用方法A()时都会调用方法B()

大堆栈是否存在问题?可能不是。在32位系统上,堆栈指针为32位。这意味着你的堆栈框架最多可以有2 ^ 32项理论。

如果使用正确,递归没有错。只要你有一个garenteed结束条款,你没事。在您的代码示例中,它是:

if (counter < 100)

当递归发生时,你需要记住两件事。

代码可读性

确保其他程序员很容易理解为什么需要递归以及导致它的原因是非常困难的。例如,在您的代码示例中,您有方法A()调用方法B(),然后调用方法A()。这很令人困惑。通常,在使用递归时,方法应调用本身,从而创建易于理解的代码。例如,这在快速排序样式函数中很常见。

避免使用.NET StackOverflowException

递归非常容易失控并导致上述异常。因此,您必须能够保证最终条款是可靠的。

在您发布的代码示例中,如果代码在方法A()中更改为counter--,则可能会出现问题。这可能是一个简单的错误,因为end子句依赖于其他方法正在做什么。其他程序员,甚至是一段时间后自己,可能会因为某种原因无意中更改此代码。这链接回代码可读性。尽量保持end子句保持递归,从而限制了这个潜在的问题。