函数调用多少会导致栈溢出

Posted

技术标签:

【中文标题】函数调用多少会导致栈溢出【英文标题】:How many function calls will cause stack overflow 【发布时间】:2012-08-27 03:57:05 【问题描述】:

android/Java 开发者您好,

当一个函数调用一个函数并且该函数调用另一个函数等等时,多少次调用(堆栈长度)会使我进入堆栈溢出?有没有一般的经验法则?

我问的原因是因为我现在对于我的 5 人纸牌游戏来说更有效(设计明智)

解决方案 1:

for(int i=0;i<100;i++)
         p1.play();
         p2.play();
         p3.play();
         p4.play();

解决方案 2:

   p1.play();    //where p1.play() calls p2.play() and so on until p4 calls p1 again.   
                 // this will go on for 100 times

我更喜欢解决方案 2,所以如果发生崩溃,我可以看到从 i=0 处的 p1 到 i=100 处的 p4 的所有函数调用

但是对于解决方案 1,堆栈要短得多,但是当发生崩溃时,我会在循环的开头看到发生崩溃的调用函数 play()

你有什么建议?我知道这是 2 合 1 的问题,但它们非常相关

谢谢大家

【问题讨论】:

为什么你没有第一次调用大多数嵌套函数。 【参考方案1】:

根据我的经验,Java 中的堆栈溢出几乎总是由于编程错误。查看典型尺寸here。

现在,您的第二个解决方案是,IMO,非常丑陋……几乎是一个编程错误。假设 N=100 是(某种程度上)游戏的持续时间,那么内存消耗(堆栈大小)随之增加听起来是错误的。我根本不喜欢那个解决方案。

当发生崩溃时,我会在循环的开头看到 在发生崩溃的地方调用函数 play()

我没有看到真正的优势。为什么不放置一个try catch 块,以便在发生崩溃时打印出迭代号?

【讨论】:

好的,这就是我一直在寻找的答案。我想我会选择第一个【参考方案2】:

这归结为递归与迭代的原则。

递归允许您以简单的方式编写算法;更容易理解和更快地实施。 递归算法可以转换为迭代算法,这将更节省内存和CPU效率。

所以决定是性能与简单性/编码工作之一。

这是一个关于这个主题的线程: Recursion or Iteration?

【讨论】:

【参考方案3】:

Java 中每个方法的frame 都有:局部变量表和操作数堆栈。这个堆栈大小是恒定的,每个方法可能有不同的大小。由于 JVM 规范,操作数堆栈大小存储在方法属性 Code 中的 max_stack 字段中:

Code_attribute 
    u2 attribute_name_index;
    u4 attribute_length;
    u2 max_stack;
    u2 max_locals;
    u4 code_length;
    u1 code[code_length];
    u2 exception_table_length;
       u2 start_pc;
        u2 end_pc;
        u2 handler_pc;
        u2 catch_type;
     exception_table[exception_table_length];
    u2 attributes_count;
    attribute_info attributes[attributes_count];

这个大小是在编译过程中计算出来的,当你点击***Exception时可能会被JVM改变。 JVM规范:

以下异常情况与 Java 虚拟机堆栈相关:如果线程中的计算需要的 Java 虚拟机堆栈超出允许范围,则 Java 虚拟机会抛出 ***Error。如果 Java 虚拟机堆栈可以动态扩展,并且尝试进行扩展,但没有足够的内存来实现扩展,或者如果没有足够的内存来为新线程创建初始 Java 虚拟机堆栈,则 Java 虚拟机器抛出 OutOfMemoryError。

总结一下:这取决于你的 JVM 被允许获得多少内存。在不同的工作站/智能手机上,您可能有不同的可用内存值。这就是为什么你不应该编写依赖于这些东西的代码。如果您认为OutOfMemoryException 可能会发生,请尝试迭代而不是递归解决您的问题。

【讨论】:

【参考方案4】:

我认为 Shivan Dragon 是对的,没有固定数量的调用,这会导致溢出。但是,您可以使用非常简单的递归函数对其进行测试:

public void stackTest(int iteration)

    System.out.println("Iteration: "+iteration); // or Log
    stackTest(iteration+1);

然后这样称呼它:

stackTest(1);

然后看看能走多远。

【讨论】:

正确,但我希望如此接近,因为每个设备都不同。但值得一试【参考方案5】:

对于创建堆栈溢出的递归或嵌套函数没有通用的经验法则。相反,它取决于堆栈上的可用内存,这可能会因底层硬件和操作系统分配而异。

如果不查看更多代码,很难确定哪种函数调用方法更适合您的情况。我会支持前一个(第一个)选项,因为它对正在发生的事情更加明确,并且它避免将可能不一定相互依赖的方法和对象实例链接在一起。如果您主要关心错误报告,您可以尝试将日志添加到您的代码中,以便更详细地了解正在发生的事情,同时查看堆栈跟踪的转储。希望这个链接也能帮助到你:http://developer.android.com/tools/debugging/index.html

【讨论】:

感谢您提供更准确的答案。如果我没有更好的答案,我会接受它:)【参考方案6】:

我认为您不能说一般数量的 x 函数调用会触发堆栈内存溢出。这取决于函数、它们的参数、它们的返回类型等,都保存在堆栈内存中,因此不同的函数可能占用不同数量的(堆栈)内存。

无论如何,您都不应该依赖那些通过尝试考虑使用了多少堆栈而接近崩溃堆栈的代码。你的代码应该永远不会溢出堆栈。

【讨论】:

信不信由你,在我的编程生涯中,我从来没有想过堆栈溢出,因为我从来没有接近它(总是一个特定的调用来执行操作)。但是当我正在开发纸牌游戏时,我注意到玩家必须互相触发,这个问题突然出现在脑海中。所以你建议我总是必须尽量减少嵌套函数调用? 100个嵌套调用太多了啊! 嗯,是的,虽然老实说 100 个嵌套调用并不多。也许受到限制的 Android 设备有一个 JVM(或其他任何设备),与桌面 PC 相比,它分配的堆栈内存更少。无论如何,如果性能是一个大问题(这里就是这种情况),Android 文档指定将方法调用保持在最低限度。所以,是的,试着让更少的方法和堆栈相关的东西被放入(堆栈)内存中。

以上是关于函数调用多少会导致栈溢出的主要内容,如果未能解决你的问题,请参考以下文章

递归调用太深,可能导致栈溢出

线程栈溢出与线程属性

Python 递归函数

python-15

如何解决栈溢出

栈溢出 缓冲区溢出的一种 缓冲区以外的存储单元被改写 优化方法之一:尾调用