PHP-为什么有人警告我的正则表达式太大


PHP - Why am I being warned that my regular expression is too large?

我想使用正则表达式来验证用户输入。我希望允许字母、数字、空格、逗号、撇号、句点、感叹号和问号的任意组合,但我也希望将输入限制在4000个字符以内。我已经提出了以下正则表达式来实现这一点:/^([a-z]|[0-9]| |,|'|'.|!|'?){1,4000}$/i

然而,当我尝试使用这个正则表达式用preg_match()在PHP中测试一个主题时,我会收到一个警告:PHP Warning: preg_match(): Compilation failed: regular expression is too large at offset 37,并且该主题无法测试。

我觉得这很奇怪,因为如果我使用无限量词,测试就会很好地通过(我在下面演示这种情况)。

为什么将重复次数限制在4000次是个问题,而无限重复不是

regex test.php:

<?php
$infinite = "/^([a-z]|[0-9]| |,|'|'.|!|'?)*$/i";        // Allows infinite repetition
$fourk    = "/^([a-z]|[0-9]| |,|'|'.|!|'?){1,4000}$/i"; // Limits repetition to 4000
$string   = "I like apples.";
if ( preg_match($infinite, $string) ){
    echo "Passed infinite repetition. 'n";
}
if ( preg_match($fourk, $string) ){
    echo "Passed maximum repetition of 4000. 'n";
}
?>

回声:

Passed infinite repetition 
PHP Warning:  preg_match(): Compilation failed: regular expression is too large at offset 37 in regex-test.php on line 16

错误是由于其LINK_SIZE,偏移量值将编译的模式大小限制为64K。这是一种预期的行为,如下所述,这并不是因为重复的限制,也不是因为编译时如何解释模式。


在这种情况下

正如Alan Moore在回答中指出的那样,所有角色都应该在同一个角色类中。我更激烈,所以允许我说这种模式是,所以错了,这让我感到尴尬
-无意冒犯,我们大多数人也试过一次。这只是试图强调,在任何情况下都不应该使用这样的构造

(x|y|z){1,4000}中有三个常见的陷阱:

  1. 只有在需要时才应使用捕获子模式(存储匹配文本的特定部分,以便提取该值或在反向引用中使用该值)。对于所有其他用例,请坚持使用非捕获组或原子组。它们表现更好,节省内存
  2. 不应重复捕获子模式,因为最后一次重复会覆盖捕获的文本
    -好的,它只能在非常的特定情况下使用
  3. 交替(使用|s)添加回溯状态。尽量减少它们是一个很好的做法。在这种情况下,正则表达式^[ !',.0-9?A-Z]{1,4000}$/i将完全匹配,不仅避免了错误,而且证明了更好的性能

链接大小

来自pcrebuild手册页中的"处理超大模式"

在编译的模式中,偏移值用于从部分到另一部分(例如,从左括号到交替元字符)。默认情况下,在8位和16位库中,两个字节的值用于这些偏移,导致编译后的模式的最大大小约为64K。

这意味着编译后的模式为交替中的每个子模式、组的每次重复存储一个偏移值。在这种情况下,偏移量不会为编译后的模式的其余部分留下内存。

这在PHP dist:的pcre_internal.h中的注释中得到了更清晰的表达

PCRE将其编译代码中的偏移量保持为2字节数量(始终以big-endian顺序存储)。例如,从子模式的开始链接到其替代方案及其终止每个偏移量使用2个字节限制了编译的regex到64K左右,这对几乎所有人来说都足够大了。


使用pcretest,我得到以下信息:

PCRE version 8.37 2015-04-28
/^([a-z]|[0-9]| |,|'|'.|!|'?){1,575}$/i
Failed: regular expression is too large at offset 36
/^([a-z]|[0-9]| |,|'|'.|!|'?){1,574}$/i
Memory allocation (code space): 65432
  • 您可以从RexEgg.com下载Windows版本


关于PCRE中的其他大小限制,你可以查看我的这篇帖子。


重写PHP中的默认LINK_SIZE

如果我们有一个真正的理由使用一个巨大的模式,并且这个模式无法通过任何方式进一步简化,那么链接大小可以增加。然而,您只能通过自己重新编译PHP来实现这一点(因此,从现在起,您的代码将不可移植)。这应该是最后的手段,前提是别无选择。

也在pcre_internal.h中评论:

宏由LINK_SIZE的值控制。这在config.h文件中默认为2,但是可以通过在命令行上使用CCD_ 9来覆盖。这在Unix系统上通过"configure"命令实现自动化。

PCRE链路大小可以配置为3:或4:

./configure -DLINK_SIZE=4

但请记住,较长的偏移量需要额外的数据,这将减慢对preg_*函数的所有调用。

如果要自己编译PHP,请参阅在Unix系统上安装或在Windows上构建自己的PHP。

查看php使用的"regex"引擎,pcre如下:http://pcre.sourceforge.net/pcre.txt在限制部分,它指出:

The maximum length of a  compiled  pattern  is  65539  (sic)
 bytes 

我的猜测是,一些正则表达式如下:

(123){1,3}

被编译成类似于的东西

(123)(123)?(123)?

这使得它大于的最大长度

虽然我同意regex编译器不应该这样做,但你真的不应该遇到这个问题。在括号内,正则表达式正好匹配特定集合中的一个字符——字符类的定义。编写正则表达式的正确方法是在一组方括号内列出所有字符,并放弃括号:

/^[a-z0-9 ,'.!?]{1,4000}$/i

这很好,正如这个演示所示。然而,导致错误的是括号(即使是未捕获的括号也会导致错误),这在我看来是不对的,即使它们是不必要的。

对我来说,问题是一个未转义的?字符

你需要逃离它不是一个,而是向前斜杠''

我的正则表达式从(?340202)变为(''?340202)