Гипотетический вопрос для вас всех, чтобы пожевать …
Недавно я ответил на другой вопрос о SO, где скрипт PHP был прерван, и это напомнило мне то, о чем я всегда думал, поэтому давайте посмотрим, может ли кто-нибудь пролить свет на него.
Рассмотрим следующее:
<?php function segfault ($i = 1) { echo "$i\n"; segfault($i + 1); } segfault(); ?>
Очевидно, что эта (бесполезная) функция бесконечно петляет. И, в конце концов, закончится память, потому что каждый вызов функции выполняется до того, как предыдущий закончен. Похоже на вилочную бомбу без разветвления.
Но … в конечном счете, на платформах POSIX сценарий будет умирать с SIGSEGV (он также умирает в Windows, но более изящно – насколько это может объяснить мои крайне ограниченные навыки отладки низкого уровня). Количество циклов варьируется в зависимости от конфигурации системы (память, выделенная для PHP, 32 бит / 64 бит и т. Д.) И ОС, но мой реальный вопрос: почему это происходит с segfault?
Если вы используете XDebug, существует максимальная глубина вложенности функции, которая контролируется установкой ini :
$foo = function() use (&$foo) { $foo(); }; $foo();
Выдает следующую ошибку:
Неустранимая ошибка: максимальный уровень вложенности функции «100» достигнут, прерывается!
Это ИМХО является гораздо лучшей альтернативой, чем segfault, поскольку он только убивает текущий скрипт, а не весь процесс.
Этот поток был включен в список внутренних дел несколько лет назад (2006). Его комментарии:
До сих пор никто не предлагал решение проблемы бесконечного цикла, которое бы удовлетворяло этим условиям:
- Никаких ложных срабатываний (т.е. хороший код всегда работает)
- Без замедления для выполнения
- Работает с любым размером стека
Таким образом, эта проблема остается нераскрытой.
Теперь, № 1, буквально невозможно решить из-за проблемы с остановкой . # 2 тривиально, если вы храните счетчик глубины стека (поскольку вы просто проверяете уровень инкрементного стека на push стека).
Наконец, проблема №3 гораздо сложнее решить. Учитывая, что некоторые операционные системы будут распределять пространство стека непересекающимся образом, реализовать его со 100% -ной точностью не будет, поскольку невозможно обеспечить переносимость размера стека или использования (для конкретной платформы это может быть возможно или даже легко, но не вообще).
Вместо этого PHP должен взять подсказку с XDebug и других языков (Python и т. Д.) И создать настраиваемый уровень вложенности (по умолчанию для Python установлено 1000) ….
Либо это, либо ловушку выделения памяти в стеке, чтобы проверить segfault перед тем, как это произойдет, и преобразовать это в RecursionLimitException
чтобы вы могли восстановить ….
Я мог быть совершенно неправ, потому что мои тесты были довольно краткими. Похоже, что 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; } ?>
Не знаете ничего о реализации PHP, но не редкость в языковой среде, чтобы оставить страницы нераспределенными в «вершине» стека, чтобы segfault возникнет, если переполнение стека. Обычно это обрабатывается внутри среды выполнения, и либо стек расширяется, либо сообщается о более элегантной ошибке, но могут быть реализации (и ситуации в других), где segfault просто позволяет подняться (или ускользнуть).