PHP - 为什么我被警告我的正则表达式太大了?

时间:2015-11-28 20:48:42

标签: php regex

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

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

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

为什么将重复限制为4000个问题,但无限重复?

正则表达式-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

3 个答案:

答案 0 :(得分:7)

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

在这种情况下

正如艾伦·摩尔在his answer中指出的那样,所有角色应该在同一个character class中。我更激烈,所以请允许我说模式是所以错误让我感到畏缩。
- 没有进攻,我们大多数人也曾尝试过。它只是试图强调不应该使用这样的结构。

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

  1. Capturing subpatterns只应在需要时使用(存储匹配文本的特定部分,以便提取该值或在backreference中使用它) 。对于所有其他用例,请坚持non-capturing groupsatomic groups。它们表现更好,节省内存。
  2. 捕获子模式should not be repeated,因为最后一次重复会覆盖捕获的文本 -OK,只能在非常的特定情况下使用
  3. Alternation| s )添加了回溯状态。尝试尽可能减少它们是一种很好的做法。在这种情况下,正则表达式^[ !',.0-9?A-Z]{1,4000}$/i将完全相同,不仅可以避免错误,还可以提高性能。
  4. LINK_SIZE

      

    来自&{34; Handling Very Large Patterns&#34; pcrebuild man page

         

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

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

      

    来自PHP dist的pcre_internal.h 评论中更明确地表达了这一点:

         

    PCRE将其编译代码中的偏移量保持为2字节数量(始终为   默认情况下以big-endian顺序存储)例如,这些用于   从子模式的开头到它的替代品和它的链接   结束。每个偏移量使用2个字节限制了编译的大小   正则表达式大约为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
    


    关于PCRE中的其他大小限制,您可以查看this post of mine

    覆盖PHP中的默认LINK_SIZE

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

      

    还在pcre_internal.h

    中发表了评论      

    宏由LINK_SIZE的值控制。   在config.h文件中默认为2,   但可以通过在命令行上使用-D来覆盖。   这是通过&#34; configure&#34;在Unix系统上实现的。命令。

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

    ./configure -DLINK_SIZE=4
    

    但请注意,较长的偏移量需要额外的数据,并且会减慢对preg_* functions的所有调用。

    如果您自己编译PHP,请参阅Installation on Unix systemsBuild your own PHP on Windows

答案 1 :(得分:2)

看看&#39;正则表达式&#39;引擎php使用,pcre here:http://pcre.sourceforge.net/pcre.txt在其声明的限制部分:

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

我的猜测是这样的一些正则表达式:

(123){1,3}

被编译成类似这样的东西

(123)(123)?(123)?

这使它大于最大长度

答案 2 :(得分:2)

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

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

这正常,正如demo所示。但是, 导致错误的括号(即使是非捕获的parens导致它),这对我来说似乎不对,即使它们是不必要的。