C++ - 在长时间的多行匹配期间,STD 正则表达式在 MSVC 中崩溃

C++ - STD regex crashes in MSVC during long multiline match

本文关键字:STD 正则表达式 MSVC 崩溃 长时间 C++      更新时间:2023-10-16

我正在尝试使用 std::regex 从源文件中提取/* ... */样式的注释。但是"regex_search"有时会在跨越多行的长匹配中崩溃(未经处理的异常(。

性病示例(不起作用(

此示例为我崩溃:

#include <iostream>
#include <regex>
int main()
{
    std::string in = "/*naaanaaaaaaaaanaaaaaaaaanaaaaaaaaaaaaanaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaanaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaanaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaanaaaaaaaaaaaaaaaaanaaaaaaaaanaaaaaaaaaaaaanaaaaaaaaanaaaaaaaaaaaaaaaaaanaaaaaaaaanaaaaaaaaaaaaanaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaanaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaanaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaanaaaaaaaaaaaaaaaaanaaaaaaaaanaaaaaaaaanaaaaaaaaan*/";
    std::regex e(".*/\*(n|.)*?\*/");
    std::smatch m;
    while (std::regex_search(in, m, e))
    {
        std::cout << m[0].str() << std::endl;
        in = m.suffix();
    }
    return 0;
}

我正在使用Visual Studio 2013,所以这可能是编译器特定的问题。

编辑:正如@T.C.在注释中指出的那样,该代码在GCC 4.9下工作,并且会引发堆栈溢出异常。这可能只是Visual C++编译器的问题,也可能只是GCC分配了更大的堆栈。

Qt示例(工作(

我尝试在Qt中实现同样的事情没有问题,所以我认为我没有犯任何错误。但我非常希望不依赖任何外部库。

QRegularExpression re(".*/\*(n|.)*?\*/");
QRegularExpressionMatchIterator it = re.globalMatch(QString(in.c_str()));
while (it.hasNext())
{
    QRegularExpressionMatch match = it.next();
    QString word = match.captured(0);
}

问题

这可能是 std::regex 实现中的错误吗?我在任何地方犯了错误吗?

我认为这不是编译器问题(如果您不使用 gcc <4.9(。正则表达式崩溃,因为获取结果的步骤量太高。尝试对此模式执行相同的操作:

/\*[\s\S]*?\*/

或者用这个模拟所有格量词:

/\*(?=((?:[^*]+|\*(?!/))*))\1\*/

(这两种模式旨在与 ECMAScript 模式配合使用,也就是说,如果我没记错的话,就是正则表达式引擎的默认模式(

关于您的原始模式:

第一个错误是以 .* 开始您的模式(由于您使用 regex_search 方法,因此不需要这样做(。由于量词默认是贪婪的,因此第一个子模式将匹配所有字符,直到每行末尾。在获得匹配项之后,正则表达式引擎需要逐个字符回溯,直到它在字符串中找到/*(请注意,如果同一行中有多个/*,则只会找到最后一个(。

第二个错误是使用类似(\n|.)*?的东西来描述所有字符,直到接下来的内容(即 */(。

使用这种构造有几个成本:

  • 使用捕获组,因此需要支付每个字符的存储成本(逐个(。
  • 您支付了更改的成本,因为大多数时候.会匹配并且\n没有任何测试(但是,这取决于您的评论的外观,但编写(?:.|\n)*?可能会更有性能。
  • 最重要的成本可能是您使用具有非贪婪量词的组,因为它强制所有字符的正则表达式引擎进入组并为每个字符离开组。如果没有惰性量词,在某些正则表达式引擎中,(?:a)+可能会比a+慢 150 倍

关于你在评论中提出的问题,我会给你一个大致的答案。

是的,步骤或回溯步骤的数量在某处受到限制。如果正则表达式引擎足够智能,它可能会在预分析期间检测到模式会导致太多工作,然后再尝试做某事,但情况并非总是如此。

要确切地知道发生了什么,您可以将正则表达式模式放在 try/catch 块中,并检查以下两个错误:

if (e.code() == std::regex_constants::error_complexity)
    std::cerr << "The complexity of an attempted match against a regular expression exceeded a pre-set level.n";
else if (e.code() == std::regex_constants::error_stack)
    std::cerr << "There was insufficient memory to determine whether the regular expression could match the specified character sequence.n";

呵。我最近在静态分析代码中遇到了同样的问题。所以这是解决方案,尽管它确实依赖于第三方库(我的(:

// http://www.benhanson.net/lexertl.html
#include <lexertl/generator.hpp>
#include <lexertl/iterator.hpp>
int main()
{
    std::string in = "/*naaanaaaaaaaaanaaaaaaaaanaaaaaaaaaaaaanaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaanaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaanaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaanaaaaaaaaaaaaaaaaanaaaaaaaaanaaaaaaaaaaaaanaaaaaaaaanaaaaaaaaaaaaaaaaaanaaaaaaaaanaaaaaaaaaaaaanaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaanaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaanaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaanaaaaaaaaaaaaaaaaanaaaaaaaaanaaaaaaaaanaaaaaaaaan*/";
    lexertl::rules rules;
    lexertl::state_machine sm;
    rules.push("[/][*](n|.)*?[*][/]", 1);
    rules.push(".|n", rules.skip());
    lexertl::generator::build(rules, sm);
    lexertl::citerator iter(in.c_str(), in.c_str() + in.size(), sm);
    lexertl::citerator end;
    for (; iter != end; ++iter)
    {
        std::cout << iter->str() << std::endl;
    }
    return 0;
}