深悉PHP正则(pcre)最大回溯/递归限制

本日,Tank问了一个问题, 对付如下的正则:

/<script>.*?<\/script>/i


当要匹配的字符串长度大于100014的时候, 就不会得出正确功效:

$reg = "/<script>.*?<\/script>/is"; $str = "<script>********</script>"; //长度大于100014 $ret = preg_replace($reg, "", $str); //返回NULL莫非正则对匹配的串有长度限制?

不是, 虽然不是, 原因是这样的, 在PHP的pcre扩展中, 提供了俩个配置项.

pcre.backtrack_limit //最大回溯数 pcre.recursion_limit //最大嵌套数

默认的backtarck_limit是100000(10万).

这个问题, 就和配置项backtrack_limit有干系. 此刻要弄清这个问题的原因, 要害就是什么是”回溯”.

这个正则, 利用非贪婪模式, 非贪婪模式匹配道理简朴来说是, 在可配也可不配的环境下, 优先不匹配. 记录备选状态, 并将匹配节制交给正则表达式的下一个匹配字符, 当之后的匹配失败的时候, 再溯, 举办匹配.

举个例子:

源字符串: aaab 正则: .*?

匹配进程开始的时候, “.*?”首先取得匹配节制权, 因为长短贪婪模式, 所以优先不匹配, 将匹配节制交给下一个匹配字符”b”, “b”在源字符串位置1匹配失败(“a”), 于是回溯, 将匹配节制交回给”.*?”, 这个时候, “.*?”匹配一个字符”a”, 并再次将节制权交给”b”, 如此重复, 最终获得匹配功效, 这个进程中一共产生了3次回溯.

此刻我们来看看文章开头的例子, 默认的backtrack_limit是100000, 而源字符串的开头是9个字符, 一共是99997个字符.

别的, 因为match函数自身的逻辑, 在文章开头的例子下, 会导致回溯计数增3(有乐趣的可以参看pcrelib/pcre_exec.c中match函数逻辑部门), 所以在匹配到"“之前, pcre中的回溯计数恰好是100000,于是就正常匹配, 退出.

而, 只要在增加一个字符, 就会导致回溯计数大于100000, 从而导致匹配失败退出.

在PHP 5.2今后, 提供了:

int preg_last_error ( void ) Returns the error code of the last PCRE regex execution.

我们应该常常查抄这个函数的返回值, 当不为零的时候说明上一个正则函数堕落, 出格的对付文章的例子, 堕落返回(PREG_BACKTRACK_LIMIT_ERROR)

最后, 在顺便说一句, 非贪婪模式导致太多回溯, 一定会有一些机能问题, 适当的该写下正则, 是可以制止这个问题的. 好比将文章开头例子中的正则修改为:

/<script>[^<]*<\/script>/i

就不会导致这么多的回溯了~

而recursion_limit限制了最大的正则嵌套层数, 假如这个值, 配置的太大, 大概会造成耗尽栈空间爆栈. 默认的100000好像有点太大了…

就好比对付一个长度为10000的字符串, 如下这个看似”简”的单正则:

//默认recursion_limit为100000 $reg = /(.+?)+/is; $str = str_pad("laruence", 10000, "a"); //长度为1万 $ret = preg_repalce($reg, "", $str);

会导致core, 这是因为嵌套太多, 导致爆栈.

虽然, 你可以通过修改栈的巨细来临时的办理这个问题, 好比修改栈空间为20M今后, 上面的代码就能正常运行, 但这必定不是最完美的解法. 基础之道, 照旧优化正则.

最后: 正则虽易, 用好却难.尤其在做大数据量的文本处理惩罚的时候, 假如正则设计不慎, 很容易导致深度嵌套, 别的思量到机能, 照旧发起能用字符串处理惩罚只管利用字符串处理惩罚取代.

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/7921.html