2011-09-06 186 views
31

你都來啃一個假設性的問題......爲什麼PHP中的無限遞歸函數會導致段錯誤?

我最近回答了另一個問題上那麼,一個PHP腳本段錯誤,它提醒我的東西,我一直在想,讓我們看看,如果任何人都可以擺脫它上面有任何光。

考慮以下幾點:

<?php 

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

    segfault(); 

?> 

顯然,這(沒用)功能無限循環。並且最終將耗盡內存,因爲每次調用函數都會在前一個函數完成之前執行。有點像叉子炸彈沒有分叉。但是......最終,在POSIX平臺上,腳本將死於SIGSEGV(它也死於Windows,但更優雅 - 只要我極其有限的低級調試技能可以說明)。循環數取決於系統配置(分配給PHP,32位/ 64位等的內存等)和操作系統,但我真正的問題是 - 爲什麼會發生段錯誤?

  • 這是PHP如何處理「內存不足」錯誤?當然,必須有更優雅的處理方式?
  • 這是Zend引擎中的錯誤嗎?
  • 有沒有什麼辦法可以從PHP腳本中更好地控制或處理這個問題?
  • 是否有任何設置通常控制可以在函數中進行遞歸調用的最大數量?
+0

現代版本的PHP(5 iirc)有遞歸的深度限制,以防止這種情況之類的事情。如果它是segfaulting,這絕對是一個應該報告的錯誤... – ircmaxell

+7

[根據PHP](https://bugs.php.net/bug.php?id=43187),這是打算的行爲。 – NullUserException

+0

如果您正在尋找具有遞歸限制的語言,請嘗試[Python](http://docs.python.org/library/sys.html#sys.setrecursionlimit) – NullUserException

回答

24

如果使用了XDebug,存在其通過ini setting控制的最大功能嵌套深度:

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

產生以下錯誤:

Fatal error: Maximum function nesting level of '100' reached, aborting!

這恕我直言,是一個更好的替代比一個段錯誤,因爲它只會殺死當前的腳本,而不是整個過程。

幾年前(2006年)在內部列表中有this thread。他的評論是:

So far nobody had proposed a solution for endless loop problem that would satisfy these conditions:

  1. No false positives (i.e. good code always works)
  2. No slowdown for execution
  3. Works with any stack size

Thus, this problem remains unsloved.

現在,#1是毫不誇張不可能由於halting problem解決。 #2是微不足道的,如果你保持一個堆棧深度的計數器(因爲你只是檢查堆棧推動增加的堆棧級別)。

最後,#3是一個更難解決的問題。考慮到有些操作系統會以非連續的方式分配堆棧空間,因此無法以100%的精度實現堆棧空間,因爲無法輕鬆獲取堆棧大小或使用情況(對於特定的平臺,即使很容易,但一般不會)。

相反,PHP應該從XDebug的和其他語言(Python的,等等)的提示,使一個可配置的嵌套級別(Python的是set to 1000默認情況下)....

要麼,或陷阱的內存分配錯誤堆棧中檢查段錯誤發生之前,並將其轉換爲RecursionLimitException,以便您可能能夠恢復....

+0

抓住SIGSEGV並拋出異常? – Demi

+0

爲什麼我之前找不到這篇文章,當時我正在尋找分段錯誤的原因。我花了數小時在暫存服務器上調試此問題。 –

4

我可能完全錯了,因爲我的測試很簡短。看起來Php只會在內存不足的情況下發生故障(並且可能試圖訪問無效的地址)。如果內存限制設置足夠低,則您將事先收到內存不足錯誤。否則,代碼段出錯並由OS處理。

不能說這是否是一個錯誤,但腳本可能不應該被允許像這樣失去控制。

請參閱下面的腳本。無論選項如何,行爲幾乎都是相同的。沒有內存限制,它在被殺之前還會嚴重降低我的電腦的性能。

<?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; 
} 
?> 
+0

那裏有很多實驗,很好地說明了問題和結果。今天早上進一步谷歌搜索後,我發現[this](http://webcache.googleusercontent.com/search?q=cache:xGfXmRpzat4J:nicktelford.net/2010/06/18/handling-segmentation-faults-in-userland -php/+ handling + segfaults + in + userland + php&cd = 1&hl = en&ct = clnk&gl = uk)(Google緩存是因爲該網站已關閉),這表明您可以捕獲並處理段錯誤 - 雖然a)我懷疑它會起作用我們正在處理的內存不足的情況,以及b)我沒有安裝PCNTL擴展的機器來測試它。 – DaveRandom

2

一無所知PHP實現,反而使段錯誤將如果堆棧溢出發生這不是在語言運行時留下的堆棧的「頂部」未分配頁少見。通常這是在運行時內部處理的,並且堆棧被擴展或者報告更優雅的錯誤,但是可能存在segfault只允許上升(或逃逸)的實現(以及其他情況)。

+0

我有點理解這背後的原因,但它確實使PHP腳本更難以調試 - 我不知道segfault是由我的腳本還是Zend引擎引起的。得到一個有意義的錯誤信息會很好,但是我認爲實際上沒有什麼可以做到的。 – DaveRandom

+0

我同意我一般不在乎讓那種表面的例外。但是我也理解可以強制這種選擇的情況 - 堆棧溢出是在語言運行時中要處理的最困難的事情之一。 –

相關問題