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

问题描述 投票:0回答:3

我正在尝试使用 std::regex 从源文件中提取

/* ... */
样式注释。但是“regex_search”有时会在跨越多行的长匹配上崩溃(未处理的异常)。

STD 示例(不起作用)

这个例子对我来说崩溃了:

#include <iostream>
#include <regex>

int main()
{
    std::string in = "/*\naaa\naaaaaaaaa\naaaaaaaaa\naaaaaaaaaaaaa\naaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\naaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\naaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\naaaaaaaaaaaaaaaaa\naaaaaaaaa\naaaaaaaaaaaaa\naaaaaaaaa\naaaaaaaaaaaaaaaaaa\naaaaaaaaa\naaaaaaaaaaaaa\naaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\naaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\naaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\naaaaaaaaaaaaaaaaa\naaaaaaaaa\naaaaaaaaa\naaaaaaaaa\n*/";
    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 实现中的错误吗?我有什么地方搞错了吗?

c++ regex std
3个回答
3
投票

我认为这不是编译器问题(如果你不使用gcc < 4.9). The regex crash because the amount of steps to obtain a result is too high. Try to do the same with this pattern:

/\\*[\\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";

1
投票

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

// http://www.benhanson.net/lexertl.html
#include <lexertl/generator.hpp>
#include <lexertl/iterator.hpp>

int main()
{
    std::string in = "/*\naaa\naaaaaaaaa\naaaaaaaaa\naaaaaaaaaaaaa\naaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\naaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\naaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\naaaaaaaaaaaaaaaaa\naaaaaaaaa\naaaaaaaaaaaaa\naaaaaaaaa\naaaaaaaaaaaaaaaaaa\naaaaaaaaa\naaaaaaaaaaaaa\naaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\naaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\naaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\naaaaaaaaaaaaaaaaa\naaaaaaaaa\naaaaaaaaa\naaaaaaaaa\n*/";
    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;
}

0
投票

我在多个简单的正则表达式上遇到了类似的问题。有时它们起作用,有时使应用程序崩溃。 Rexex 对象在堆栈上分配,以防止草率编码。我怀疑筹码太少了。

然后我使用

new()
构造函数分配正则表达式对象。例如:

std::regex* myregexp = new std::regex("abc");
和/或
std::smatch* m = new std::smatch();
等..只有本地指针。

这种方式在 GCC 和 Linux 下对我有用。现在的电脑内存充足。

Try
Catch
编码可能有助于处理这种情况。

当然,如果你在Arduino平台(非常小的内存设备)上进行正则表达式,你可能会遇到很多麻烦...

© www.soinside.com 2019 - 2024. All rights reserved.