微软的std :: regex实现

时间:2013-07-13 22:37:11

标签: c++ regex c++11 stack-overflow standard-library

我一直在使用<regex>库(Microsoft Visual Studio 2012:Update 3),尝试使用它来为我的应用程序实现稍微更安全的加载过程,并且遇到了一些问题(cf 。Regular Expression causing Stack OverflowConcurrently using std::regex, defined behaviour?ECMAScript Regex for a multilined string)。

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

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

12 (error_stack)

使用默认堆栈大小(4kB提交和1MB保留)进行编译将导致抛出Stack Overflow异常;并且在更改堆栈大小(8kB提交和2MB保留)时,会导致#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; } } 抛出错误代码std::regex_error

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

提前致谢!

2 个答案:

答案 0 :(得分:3)

忘记<regex> - 至少现在,可能是好的。在我看来,规范被打破并无法使用;但即使不是这样,至少目前的实施方式是,而且可能会持续多年。

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

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

顺便说一句,我们今天在聊天中讨论了这个话题。如果你有半个小时,you can read my detailed rant and some interesting counter-points

答案 1 :(得分:2)

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

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

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

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

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

然后创建终止字符串:

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

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

顺便说一句,我觉得奇怪的是你只识别起始分隔符,如果它位于一行的 end ,而结束分隔符是否位于 start of a line。我倾向于要求它们都是完整的行。如果用我的两步法替换regex-with-back-reference,那么实现它相对容易。这可能被认为是另一个暗示,正则表达式与后向引用并不是真正正确的方法。