Различные результаты для модификатора unicode / multibyte и mb_ereg_replace

Это регулярное выражение кажется очень проблематичным:

(((?!a).)*)*a\{ 

Я знаю, что регулярное выражение ужасно. Здесь не вопрос.

при тестировании с помощью этой строки:

 AAAAAAAAAAAAAA{AA 

Буквы A и a могут быть заменены почти чем угодно и привести к одной и той же проблеме.

Эта пара регулярных выражений и тестовых строк конденсируется. Полный пример можно найти здесь .

Это код, который я использовал для тестирования:

 <?php $regex = '(((?!a).)*)*a\\{'; $test_string = 'AAAAAAAAAAAAAA{AA'; echo "1:".mb_ereg_replace('/'.$regex.'/','WORKED',$test_string)."\n"; echo "2:".preg_replace('/'.$regex.'/u','WORKED',$test_string)."\n"; echo "3:".preg_replace('/'.$regex.'/','WORKED',$test_string)."\n"; 

Результаты можно посмотреть здесь:

http://3v4l.org/Yh6FU

Идеальный результат заключается в том, что возвращается одна и та же тестовая строка, поскольку регулярное выражение не совпадает.

При использовании preg_replace с модификатором u он должен иметь те же результаты, что и mb_ereg_replace соответствии с этим комментарием:

регулярное выражение многобайтовых строк php

mb_ereg_replace работает точно так, как должно. Он возвращает тестовую строку, потому что регулярное выражение не совпадает.

Однако preg_replace для версий PHP, отличных от 4.3.4 – 4.4.5, 4.4.9 – 5.1.6, похоже, не работает.

  • Для некоторых версий PHP в результате возникает ошибка:

    Процесс завершен с кодом 139.

  • Для некоторых других версий PHP результат равен NULL
  • Для остальных, mb_ereg_replace еще не было сделано

Кроме того, удаление только одной буквы из строки или регулярного выражения, по-видимому, полностью изменяет, какие версии PHP имеют результаты.

Судя по этому комментарию:

регулярное выражение многобайтовых строк php

ereg* следует избегать, что имеет смысл, поскольку оно медленнее и поддерживает меньше, чем preg* . Это делает нежелательным использование mb_ereg_replace . Однако нет опции mb_preg_replace , поэтому это единственный вариант, который работает.

Итак, мой вопрос:
Есть ли альтернатива mb_ereg_replace которая бы корректно работала для данной строки и пары регулярных выражений?

Знаете ли вы разницу между (...) и (?:...) ?

(...) … это определяет группу маркировки. Строка, найденная выражением в круглых скобках, внутренне хранится в переменной для обратного ссылок.

(?:...) … это определяет группу без маркировки. Строка, найденная выражением в круглых скобках, не хранится внутри. Такая группа без маркировки часто используется для применения выражения несколько раз в строке.

Теперь давайте посмотрим на ваше выражение (((?!a).)*)*a\{ The complexity of matching expression has exceeded available resources (((?!a).)*)*a\{ который при использовании в регулярном выражении Perl находит в текстовом редакторе UltraEdit, приводит к сообщению об ошибке The complexity of matching expression has exceeded available resources .

(?!a). … символ должен быть найден, где следующий символ не является буквой «a». Хорошо. Но вы хотите найти строку с 0 или более символами до буквы «a». Ваше решение: ((?!a).)*)

Это нехорошее решение, так как движок теперь на каждом персонаже смотрит на букву «a», а если следующий символ не является «a», сопоставьте символ, сохраните его как строку для обратного ссылки, а затем продолжите следующий символ. На самом деле я даже не знаю, что происходит внутри, когда множитель используется в группе маркировки, как это делается здесь. Множитель никогда не должен использоваться в группе маркировки. Так лучше было бы (?:(?!a).)* .

Затем вы расширите выражение до (((?!a).)*)* . Еще одна маркирующая группа с мультипликатором?

Похоже, вы хотите отметить всю строку, не содержащую буквы «a». Но в этом случае было бы лучше использовать: ((?:(?!a).)*) Поскольку это определяет 1 и только 1 группу маркировки для строки, найденной внутренним выражением.

Таким образом, лучшим выражением будет ((?:(?!a).)*)a\{ поскольку в настоящее время существует только одна группа маркировки без множителя в группе маркировки. Теперь движок точно знает, какую строку хранить в переменной.

Гораздо быстрее было бы ([^a]*?)a\{ поскольку это определение нежелательного отрицательного символьного класса также соответствует строке из 0 или более символов слева от a{ не содержащей буквы «a». Посмотрите вперёд, следует избегать, если это не необходимо, поскольку это позволяет избежать обратного хода.


Я не знаю исходный код функций PHP mb_ereg_replace и preg_replace которые необходимо будет изучить с помощью выражения шаг за шагом, чтобы узнать, в чем именно причина разных результатов.

Однако выражение (((?!a).)*)*a\{ приводит к определенному результату в тяжелой рекурсии, поскольку оно не определено, когда нужно прекратить сопоставление данных и что хранить временно. Таким образом, обе функции (скорее всего) выделяют все больше памяти из стека и, возможно, также из кучи до тех пор, пока не произойдет переполнение стека или исключение «недостаточно свободной памяти».

Код выхода 139 – это ошибка сегментации (нарушение границы памяти), вызванная переполнением стека, или NULL был возвращен при выделении большего количества памяти из кучи с помощью malloc (), и возвращаемое значение NULL было проигнорировано. Я полагаю, возвращение NULL по malloc () является причиной кода выхода 139.

Таким образом, разница делает большую часть ошибок, например, ошибкой обработки исключений двух функций. Причиной различного поведения этого выражения может быть захват исключения памяти или подсчет рекурсивных итераций с выходом на слишком многие из них, чтобы предотвратить исключение памяти до того, как оно действительно произойдет.

Трудно дать определенный ответ, что отличает, не зная исходный код функций mb_ereg_replace и preg_replace , но, на мой взгляд, это не имеет preg_replace значения.

Выражение (((?!a).)*)*a\{ A (((?!a).)*)*a\{ A (((?!a).)*)*a\{ всегда приводит к тяжелой рекурсии, о которой Сэм уже сообщал в своем первом комментарии. Более чем 119000 шагов (= вызовы функций) во время замены в строке с 17 символами являются сильным признаком того, что что-то не так с выражением. Выражение может использоваться для того, чтобы функция или целое приложение (интерпретатор PHP) запускались в ненормальную обработку ошибок, но не для реальной замены. Таким образом, это выражение хорошо для разработчиков функций PHP для проверки обработки ошибок в бесконечной рекурсии, но не для реальной операции замены.


Полное регулярное выражение, используемое в связанной песочнице PHP:

 (?<!<br>)(?<!\s)\s*(\((?:(?:(?!<br>|\(|\)).)*(?:\((?:(?!<br>|\(|\)).)*\))?)*?\))\s*(\{) 

Трудно проанализировать эту строку поиска в этой форме.

Итак, давайте посмотрим на строку поиска, как на фрагмент кода с углублениями для лучшего понимания условий и циклов в этом выражении.

 (?<!<br>)(?<!\s)\s* ( \( (?: (?: (?!<br>|\(|\)). )* (?: \( (?: (?!<br>|\(|\)). )* \) )? )*? \) ) \s* (\{) 

Надеюсь, теперь легче увидеть рекурсию в этой строке поиска. Дважды тот же блок, но не в порядке последовательности, а во вложенном порядке – классическая рекурсия.

Кроме того, все выражения, включая вложенные выражения, образующие рекурсию перед финалом (\{) которые могут соответствовать любому символу, имеют множители * или ? которое может существовать, но не должно существовать . Наличие { является единственным реальным условием для всей строки поиска. Все остальное является необязательным, и это не очень хорошо из-за рекурсии в этой строке поиска.

Это очень плохо для выражения рекурсивного поиска, если совершенно неясно, с чего начать и где прекратить выбирать символы, поскольку это приводит к бесконечной рекурсии до аномального выхода.

Позвольте мне объяснить эту проблему простым выражением типа [A-Za-z]+([az]+)

1 или более букв в верхнем или нижнем регистре, за которыми следуют 1 или более символов в нижнем регистре (и поиск с учетом регистра разрешен). Простой, не так ли.

Но второй класс символов определяет набор символов, который является подмножеством набора символов, определенных определением первого класса. И это не хорошо.

Что должно быть помечено выражением в круглых скобках в строке, такой как York ?

ork или rk или просто k или даже ничего, потому что никакая совпадающая строка, найденная как первый класс символов, не может соответствовать уже всему слову, и поэтому ничего не осталось для второго класса символов?

Библиотека регулярных выражений Perl решила такую ​​общую проблему, объявив мультипликаторы * и + по умолчанию как жадные, кроме ? используется после множителя, что приводит к противоположному совпадающему поведению. Эти 2 дополнительные правила уже помогают в решении этой проблемы.

Поэтому используемое здесь выражение отмечает только k и с [A-Za-z]+?([az]+) строка ork отмечена и с [A-Za-z]+?([az]+?) Просто первый o отмечен.

И есть еще одно правило: положительно положительный результат по отрицательному результату. Это дополнительное правило позволяет избежать того, что первый класс символов выбирает уже все слово York .

Таким образом, основная проблема с частично или полностью перекрывающимися наборами символов решена.

Но что происходит, если такое выражение помещается в рекурсию и делает его еще более сложным с помощью lookahead / lookbehind и backtracking, а обратное отслеживание выполняется не только одним символом, но даже несколькими символами?

Четко ли определено, где начинать и останавливать выбор символов для каждой части выражения всей строки поиска?

Нет.

С помощью строки поиска, где нет четкого правила, какая часть строки поиска выбрана той частью выражения поиска, каждый результат более или менее достоверен, включая неожиданные.

Кроме того, это может случиться легко из-за отсутствия условий запуска / остановки, когда функции полностью не работают, чтобы применить выражение в строке и выйти из строя.

Аномальный выход при применении поисковой строки, безусловно, всегда является неожиданным результатом для человека, который использовал выражение поиска.

Различные версии функций поиска могут возвращать разные результаты в выражении, которое позволяет функциям поиска работать в выходе ненормальной функции. Разработчики функций поиска непрерывно изменяют программный код функций поиска, чтобы лучше обнаруживать и обрабатывать выражения поиска, что приводит к бесконечной рекурсии, поскольку это просто проблема безопасности. Поиск регулярных выражений, выделяющих все больше или больше памяти из стека приложения или всей ОЗУ, очень проблематичен для обеспечения безопасности, стабильности и доступности всей машины, на которой работает это приложение. И PHP используется в основном на серверах, которые не должны перестать работать, потому что рекурсивное выделение памяти занимает все больше ОЗУ с сервера, так как это, наконец, уничтожит весь сервер.

Именно по этой причине вы получаете разные результаты в зависимости от используемой версии PHP.

Я очень долго смотрел на ваше полное выражение поиска и позволял ему запускать несколько раз в строке примера. Но, честно говоря, я не мог узнать, что нужно найти и что следует игнорировать из выражения слева от (\{) .

Я понимаю части выражения, но почему существует рекурсия в строке поиска?

Какова цель отрицательного lookbehind (?<!\s) на \s* ?

\s* соответствует 0 или более пробелам, поэтому для выражения «предыдущий символ, не являющийся пробелом», для меня не подходит. Отрицательный lookbehind просто бесполезен в моей точке зрения и просто увеличивает сложность всего выражения. И это только начало.

Я абсолютно уверен, что то, что вы действительно хотите, может быть достигнуто с гораздо более простым выражением, не имеющим рекурсии, в результате чего выходят ненормальные функции в зависимости от искомой строки и со всеми или почти всеми шагами возврата.