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

本文关键字:调用 跟踪 堆栈 巨大 | 更新日期: 2023-09-27 18:05:46

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

int counter = 0;
private void A ()
{
    Debug.Log (System.Environment.StackTrace);
    counter++;
    B ();
}
private void B ()
{
    counter++;
    if (counter < 100)
        A ();
}

这个编译和工作很好,并在预期的100次迭代后停止。然而,我有点担心,到最后,堆栈跟踪变得相当庞大。

我试着搜索这种模式,但没有找到任何东西。做这样的事情通常是不允许的,还是可以的,只要它正确地终止?

编辑:关于使用this的澄清:这实际上是我的程序的主循环,应该永远持续下去;它不是任何递归函数。基本上我有一个大循环,但是我把它分成了多个小的方法因为各种原因,它们以这种方式互相调用。从功能上讲,这与把所有代码放在一个巨大的while循环中是一样的,但我担心的是堆栈跟踪。

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

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

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

注意:

与堆栈大小相比,

100个函数调用不算什么。

您正在做的是一个分两步递归调用,这与您扫描目录结构

所做的非常相似

伪代码:

Function ScanDirectory CurrentDirectory
    DirectoryList.Add CurrentDirectory
    For each Directory in CurrentDirectory
        ScanDirectory Directory

但是一个递归函数总是必须很好地控制,因为如果递归没有限制,你会得到一个了不起的StackOverflow异常。

目录扫描是完美的,可以看到límits,文件系统不允许有超过256的深度,所以你可以确定递归限制将是256。

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

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

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

一个重要的例外是尾递归:许多编译器会对它进行优化,让具有线性堆栈要求的算法运行到完成。

每次调用一个方法时,该方法都被放到堆栈跟踪中。这样,当当前方法返回时,它就知道返回到哪里。

在您的代码示例中,您正在递归地调用方法A() ,如其他答案中所解释的那样。如果你仔细观察你的堆栈跟踪,你会注意到will看起来像这样:
A() which calls...
B() which calls...
A() which calls...
B() which calls...
A() which calls...
B() which calls...
....

堆栈跟踪比预期的要大的原因可能是每次调用B()方法时都调用了A()方法。

是一个大的堆栈跟踪问题吗?可能不是。在32位系统上,堆栈指针是32位的。这意味着你可以在你的堆栈帧上有2^32个项目的理论最大值。

如果使用正确,递归是没有问题的。只要你有一个约定的结束子句,你就可以了。在您的代码示例中,它是:
if (counter < 100)

当递归发生时,您需要记住两件事:

代码可读性

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

避免。net StackOverflowException

递归很容易失去控制,导致上面提到的异常。因此,你必须能够保证你的end子句是可靠的。

在您发布的代码示例中,如果代码在方法A()中更改为counter--,您可能会遇到问题。这可能是一个容易犯的错误,因为end子句依赖于其他方法的操作。其他程序员,甚至是一段时间后的您自己,可能会因为这样或那样的原因无意中更改此代码。这又回到了代码的可读性。尽量保持end子句保留递归本身,从而限制这种潜在的问题。