为什么PHP中的无限递归函数会导致隔离故障?

2022-08-30 14:18:59

一个假设的问题,让你们所有人都咀嚼...

我最近回答了另一个关于SO的问题,其中PHP脚本正在隔离,它让我想起了我一直想知道的事情,所以让我们看看是否有人能对此有所了解。

请考虑以下事项:

<?php

  function segfault ($i = 1) {
    echo "$i\n";
    segfault($i + 1);
  }

  segfault();

?>

显然,这个(无用的)函数无限循环。最终,将耗尽内存,因为对函数的每次调用都会在前一个调用完成之前执行。有点像没有分叉的叉子炸弹。

但。。。最终,在POSIX平台上,脚本将与SIGSEGV一起死亡(它也在Windows上死亡,但更优雅 - 就我极其有限的低级调试技能而言)。循环的数量取决于系统配置(分配给PHP,32位/ 64位等的内存)和操作系统,但我真正的问题是 - 为什么会发生在segfault上?

  • 这是否只是PHP处理“内存不足”错误的方式?当然,一定有一种更优雅的方式来处理这个问题吗?
  • 这是Zend引擎中的错误吗?
  • 有没有办法从PHP脚本中更优雅地控制或处理它?
  • 是否有任何设置通常控制函数中可以进行的最大递归调用数?

答案 1

如果使用 XDebug,则有一个由 ini 设置控制的最大函数嵌套深度:

$foo = function() use (&$foo) { 
    $foo();
};
$foo();

产生以下错误:

致命错误:已达到最大函数嵌套级别“100”,正在中止!

恕我直言,这是一个比segfault更好的选择,因为它只会杀死当前的脚本,而不是整个过程。

几年前(2006年)在内部列表中有这个线程。他的评论是:

到目前为止,还没有人为无限循环问题提出满足这些条件的解决方案:

  1. 没有误报(即好的代码总是有效的)
  2. 执行速度不会减慢
  3. 适用于任何堆栈大小

因此,这个问题仍然不受欢迎。

现在,由于停止问题,#1几乎不可能解决。如果您保留堆栈深度计数器,则#2是微不足道的(因为您只是在堆栈推送时检查增量堆栈级别)。

最后,#3是一个更难解决的问题。考虑到某些操作系统将以不连续的方式分配堆栈空间,因此不可能以100%的准确性实现,因为不可能移植地获得堆栈大小或使用情况(对于特定平台,它可能是可能的,甚至可能很容易,但不是一般的)。

相反,PHP应该从XDebug和其他语言(Python等)中获取提示,并制定一个可配置的嵌套级别(默认情况下,Python设置为1000)。

要么是这样,要么是在堆栈上捕获内存分配错误,以便在发生之前检查segfault并将其转换为a,以便您可以恢复....RecursionLimitException


答案 2

我可能完全错了,因为我的测试相当简短。似乎Php只有在内存不足时才会出现错误(并且可能尝试访问无效地址)。如果设置了内存限制并且足够低,您将事先收到内存不足错误。否则,代码 seg 会出错,并由操作系统处理。

不能说这是否是一个错误,但脚本可能不应该被允许像这样失控。

请参阅下面的脚本。无论选项如何,行为实际上都是相同的。如果没有内存限制,它也会在计算机被杀死之前严重减慢计算机的速度。

<?php
$opts = getopt('ilrv');
$type = null;
//iterative
if (isset($opts['i'])) {
   $type = 'i';
}
//recursive
else if (isset($opts['r'])) {
   $type = 'r';
}
if (isset($opts['i']) && isset($opts['r'])) {
}

if (isset($opts['l'])) {
   ini_set('memory_limit', '64M');
}

define('VERBOSE', isset($opts['v']));

function print_memory_usage() {
   if (VERBOSE) {
      echo memory_get_usage() . "\n";
   }
}

switch ($type) {
   case 'r':
      function segf() {
         print_memory_usage();
         segf();
      }
      segf();
   break;
   case 'i':
      $a = array();
      for ($x = 0; $x >= 0; $x++) {
         print_memory_usage();
         $a[] = $x;
      }
   break;
   default:
      die("Usage: " . __FILE__ . " <-i-or--r> [-l]\n");
   break;
}
?>

推荐