2

我一直在使用该<regex>库(Microsoft Visual Studio 2012:Update 3),尝试使用它为我的应用程序实现一个稍微安全的加载过程,并且遇到了一些初期困难(参见正则表达式导致堆栈溢出同时使用 std::regex, defined behavior?ECMAScript Regex 用于多行字符串)。

通过使用此处建议的正则表达式,我解决了最初的麻烦(导致堆栈溢出等),并且运行良好;但是,如果我的文件太大,则会导致堆栈溢出(我通过增加堆栈提交和保留大小来规避),或者如果堆栈大小足够大而不会导致堆栈溢出,则会导致std::regex_errorwith 错误代码12 (error_stack)

这是一个复制问题的独立示例:

#include <iostream>
#include <string>
#include <regex>

std::string szTest = "=== TEST1 ===\n<Example1>:Test Data\n<Example2>:More Test Data\n<Example3>:Test\nMultiline\nData\n<Example4>:test_email@test.com\n<Example5>:0123456789\n=== END TEST1 ===\n=== TEST2 ===\n<Example1>:Test Data 2\n<Example2>:More Test Data 2\n<Example3>:Test\nMultiline\nData\n2\n<Example4>:test_email2@test.com\n=== END TEST2 ===\n=== TEST3 ===\n<Example1>:Random Test Data\n<Example 2>:More Random Test Data\n<Example 3>:Some\nMultiline\nRandom\nStuff\n=== END TEST3 ===\n\
                      === TEST1 ===\n<Example1>:Test Data (Second)\n<Example2>:Even More Test Data\n<Example3>:0123456431\n=== END TEST1 ===";

int main()
{
    static const std::regex regexObject( "=== ([^=]+) ===\\n((?:.|\\n)*)\\n=== END \\1 ===", std::regex_constants::ECMAScript | std::regex_constants::optimize );

    for( std::sregex_iterator itObject( szTest.cbegin(), szTest.cend(), regexObject ), end; itObject != end; ++itObject )
    {
        std::cout << "Type: " << (*itObject)[1].str() << std::endl;
        std::cout << "Data: " << (*itObject)[2].str() << std::endl;

        std::cout << "-------------------------------------" << std::endl;
    }
}

使用默认堆栈大小(4kB 提交和 1MB 保留)编译它会导致抛出堆栈溢出异常;并且在更改堆栈大小(8kB 提交和 2MB 保留)时,会导致std::regex_error抛出错误代码12 (error_stack)

我可以做些什么来防止这些错误,或者仅仅是正则表达式库被设计为仅用于小字符串(即 DoB 检查等)?

提前致谢!

4

2 回答 2

3

忘记<regex>——至少现在,可能是永远的。在我看来,规范已损坏且无法使用;但即使不是,至少当前的实现是,并且可能会在未来几年内实现。

这是因为所有主要供应商都从头开始实施自己的正则表达式引擎,而不是依赖于现有的、经过尝试和测试的库。这是一项巨大的努力。

我的建议:现在使用另一个正则表达式库并给予<regex>广泛的支持。替代方案是Boost.RegexBoost.Xpressive和(C 风格)库,例如PCREOniguruma

顺便说一句,我们今天在聊天中讨论了这个话题。如果你有半个小时的时间,你可以阅读我详细的咆哮和一些有趣的反驳点

于 2013-07-13T22:45:01.957 回答
2

问题是反向引用 ( \1)。反向引用是邪恶的,或者至少在一般情况下很难实现,并且不容易识别非一般情况。

在您的情况下,问题在于正则表达式的第一个匹配项将从 first=== TEST1 ===last === END TEST1 ===。这不是你想要的,但它是正则表达式的工作方式。(“最长的最左边规则”。)理论上,仍然可以匹配正则表达式而不杀死堆栈,但我怀疑您使用的正则表达式库是否足够聪明以进行优化。

((?:.|\\n)*)您可以通过使数据部分 ( )非贪婪来修复正则表达式以匹配您希望它匹配的内容:将其更改为((?:.|\\n)*?). 这也可能解决堆栈爆炸问题,因为它会导致正则表达式在爆炸堆栈之前更早地匹配。但我不知道它是否能正常工作;我真的对MS实施一无所知。

在我看来,你应该避免反向引用,即使这意味着你的代码有点复杂。我要做的是首先匹配:

 === ([^=]+) ===\n

然后创建终止字符串:

 "\n=== END " + match[1].str() + " ==="

然后find()是终止字符串。这意味着您不能再使用正则表达式库的迭代器,这是不幸的,但循环仍然非常简单。

顺便说一句,我觉得奇怪的是,如果它位于行尾,则仅识别开始分隔符,如果它位于,则识别结束分隔符。我的倾向是要求他们两个都是完整的行。如果您用我的两步方法替换 regex-with-back-reference,则相对容易实现。这可能被认为是另一个暗示正则表达式与反向引用并不是真正正确的方法。

于 2013-07-14T05:49:20.753 回答