【问题标题】:Why does std::regex_iterator cause a stack overflow with this data?为什么 std::regex_iterator 会导致此数据的堆栈溢出?
【发布时间】:2012-10-10 20:47:46
【问题描述】:

我一直在使用std::regex_iterator 来解析日志文件。我的程序已经运行了好几个星期,并且已经解析了数百万条日志行,直到今天,今天我针对一个日志文件运行它并得到了堆栈溢出。事实证明,日志文件中只有一个日志行导致了问题。有谁知道为什么我的正则表达式会导致如此大规模的递归?这是一个显示问题的小型自包含程序(我的编译器是 VC2012):

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

using namespace std;

std::wstring test = L"L3  T15356 79726859 [CreateRegistryAction] Creating REGISTRY Action:\n"
                L"  Identity: 272A4FE2-A7EE-49B7-ABAF-7C57BEA0E081\n"
                L"  Description: Set Registry Value: \"SortOrder\" in Key HKEY_CURRENT_USER\\Software\\Hummingbird\\PowerDOCS\\Core\\Plugins\\Fusion\\Settings\\DetailColumns\\LONEDOCS1\\Search Unsaved\\$AUTHOR.FULL_NAME;DOCSADM.PEOPLE.SYSTEM_ID\n"
                L"  Operation: 3\n"
                L"  Hive: HKEY_CURRENT_USER\n"
                L"  Key: Software\\Hummingbird\\PowerDOCS\\Core\\Plugins\\Fusion\\Settings\\DetailColumns\\LONEDOCS1\\Search Unsaved\\$AUTHOR.FULL_NAME;DOCSADM.PEOPLE.SYSTEM_ID\n"
                L"  ValueName: SortOrder\n"
                L"  ValueType: REG_DWORD\n"
                L"  ValueData: 0\n"
                L"L4  T15356 79726859 [CEMRegistryValueAction::ClearRevertData] [ENTER]\n";

int wmain(int argc, wchar_t* argv[])
{
    static wregex rgx_log_lines(
        L"^L(\\d+)\\s+"             // Level
        L"T(\\d+)\\s+"              // TID
        L"(\\d+)\\s+"               // Timestamp
        L"\\[((?:\\w|\\:)+)\\]"     // Function name
        L"((?:"                     // Complex pattern
          L"(?!"                    // Stop matching when...
            L"^L\\d"                // New log statement at the beginning of a line
          L")"                      
          L"[^]"                    // Matching all until then
        L")*)"                      // 
        );

    try
    {
        for (std::wsregex_iterator it(test.begin(), test.end(), rgx_log_lines), end; it != end; ++it)
        {
            wcout << (*it)[1] << endl;
            wcout << (*it)[2] << endl;
            wcout << (*it)[3] << endl;
            wcout << (*it)[4] << endl;
            wcout << (*it)[5] << endl;
        }
    }
    catch (std::exception& e)
    {
        cout << e.what() << endl;
    }

    return 0;
}

【问题讨论】:

  • 复杂模式部分似乎是造成它的原因。不知道为什么。
  • 我敢打赌它在 perl 中没问题,我还不太信任 std::regex
  • @Benj Wut? FUD。它可能是一个指数级的行为不端的正则表达式。大多数情况下,它是关于嵌套的 kleene 星。尝试使用非贪婪匹配,或者尽可能使用+ 而不是*。还要注意重复组中的选项。最好的建议...从小处着手。逐步建立。每一步测试你的正则表达式。
  • @Benj 公平地说,GCC 显然还不支持这种迭代方式根本 :(
  • @sehe 是的,我会给 MS 一些荣誉,如果他们努力工作并且实际上领先于游戏,正则表达式是一个领域。尽管 gcc 在 C++11 的大多数其他部分都处于领先地位。

标签: c++ regex c++11


【解决方案1】:

在每个字符上测试的负前瞻模式对我来说似乎是个坏主意,而且您尝试做的事情并不复杂。您想匹配 (1) 行的其余部分,然后 (2) 任意数量的以下 (3) 行以 L\d 以外的其他内容开头(小错误;见下文):(另一个编辑:这些是正则表达式;如果你想把它们写成字符串文字,你需要把\改成\\。)

 .*\n(?:(?:[^L]|L\D).*\n)*
 |   |  |
 +-1 |  +---------------3
     +---------------------2

在 Ecmascript 模式下,. 不应匹配 \n,但您始终可以将该表达式中的两个 .s 替换为 [^\n]

编辑添加:我意识到如果在日志条目结束之前有一个空行,这可能不起作用,但这应该涵盖这种情况;我将. 更改为[^\n] 以获得更高的精度:

 [^\n]*\n(?:(?:(?:[^L\n]|L\D)[^\n]*)?\n)*

【讨论】:

  • 干得好 ;-) 这行得通,我没有想到这可以在没有负前瞻的情况下完成。
  • 值得为后人指出,我确实需要按照您的建议使用[^\n]
  • @Benj 很高兴知道;我没有 VC 来尝试一下。我假设您使用 [^] 来表示 [^L] 实际上也将匹配空行的“任何字符”。如果发生这种情况,我会稍作修改。
  • @Benj,这可能是因为在 Windows 上,一个空行实际上有一个 \r 。我猜。
【解决方案2】:

正则表达式似乎没问题;至少其中没有任何东西会导致灾难性的回溯。

我认为优化正则表达式的可能性很小,减少了堆栈的使用:

static wregex rgx_log_lines(
    L"^L(\\d+)\\s+"             // Level
    L"T(\\d+)\\s+"              // TID
    L"(\\d+)\\s+"               // Timestamp
    L"\\[([\\w:]+)\\]"          // Function name
    L"((?:"                     // Complex pattern
      L"(?!"                    // Stop matching when...
        L"^L\\d"                // New log statement at the beginning of a line
      L")"                      
      L"[^]"                    // Matching all until then
    L")*)"                      // 
    );

set the ECMAScript option了吗?否则,我怀疑正则表达式库默认为 POSIX 正则表达式,并且不支持前瞻断言。

【讨论】:

  • 遗憾的是std::regex 没有多行正则表达式的概念(与 perl 不同)。所以. 不能跨行使用,^$ 确实表示行的开始/结束。这些锚点实际上在 perl 中会根据您是处于单行/多行模式而发生变化。
  • @Benj:嗯,好的,那么这对这个正则表达式有好处。我猜我的版本仍然会导致 StackOverflow 吗?
  • 我可能会失明 :-) 但是你改变了什么?那个正则表达式不一样吗?
  • 我已经删除了替换 (?:\\w|\\:) 以支持 charclass [\\w:]。但我同时查看了文档 - 如果您还没有设置 ECMAScript 选项,可能需要设置。
  • 啊,是的!可悲的是,我仍然得到 SO。关于 Ecmascript 选项的有趣点。我试过了,但它的工作原理是一样的。有趣的是,如果没有这个选项,前瞻肯定会起作用,因为我知道该表达式适用于所有其他数百万条日志行。
猜你喜欢
  • 1970-01-01
  • 2014-12-20
  • 1970-01-01
  • 1970-01-01
  • 2018-07-01
  • 2010-09-11
  • 1970-01-01
  • 2011-01-13
  • 1970-01-01
相关资源
最近更新 更多