C ++ - регулярное выражение STD вылетает в MSVC во время длинного многострочного соответствия

0

Я пытаюсь извлечь /*... */ комментарии стиля из исходных файлов, используя 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, поэтому это может быть проблемой, связанной с компилятором.

Изменить. Как отмечал @TC в комментариях, код работает под 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? Я допустил ошибку?

  • 0
    У меня работает (clang ++ на OS X). Однако регулярное выражение "/\\*(\n|.)*\\*/" должно делать то же самое, я не прав?
  • 0
    @tnull Первый .* выражения не важен для примера, но я использовал его, потому что хотел уловить уровень отступа комментария. А если я пропущу ? часть, то я думаю, что это будет захватывать несколько комментариев, таких как /* ... */ /* ... */ как один огромный комментарий.
Показать ещё 3 комментария
Теги:
std

2 ответа

2

Я думаю, что это не проблема компилятора (если вы не используете gcc <4.9). Крушение регулярного выражения, потому что количество шагов для получения результата слишком велико. Попробуйте сделать то же самое с этим шаблоном:

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

или с этим, который эмулирует притяжательный квантор:

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

(Эти два шаблона предназначены для работы с режимом ECMAScript, то есть, если я не ошибаюсь, режим по умолчанию вашего механизма регулярных выражений)

о вашем оригинальном шаблоне:

Первая ошибка заключается в том, чтобы начать свой шаблон с помощью .* (Это не требуется, так как вы используете метод regex_search). Поскольку квантификатор по умолчанию жадный, этот первый подшаблон будет соответствовать всем символам до конца каждой строки. После получения соответствия движок регулярного выражения должен возвращать символ по символу, пока не найдет /* в строке (обратите внимание, что если у вас несколько /* в одной строке, будет найдено только последнее).

Вторая ошибка - использовать что-то вроде (\\n|.)*? для описания всех символов до следующего (т.е. */).

Использование такого типа конструкции имеет несколько затрат:

  • вы используете группу захвата, поэтому вы оплачиваете стоимость хранения каждого символа (один за другим).
  • вы оплатили стоимость чередования, потому что большую часть времени . будет соответствовать, а \\n будет тестироваться ничем (однако это зависит от того, как выглядят ваши комментарии, но запись (?:.|\\n)*? может быть более эффективной).
  • наиболее важная стоимость, вероятно, заключается в том, что вы используете группу с не-жадным квантификатором, потому что она заставляет механизм регулярных выражений для всех символов вводить группу и оставлять группу для каждого символа. Без ленивого квантификатора в некотором двигателе регулярного выражения (?:a)+ a+ (?:a)+ может быть в 150 раз медленнее, чем a+

На вопрос, который вы задаете в комментариях, я дам вам общий ответ.

Да, количество шагов или шагов отступлений где-то ограничено. Если механизм регулярных выражений достаточно умен, во время предварительного анализа он может обнаружить, что шаблон заставит слишком много работать, прежде чем пытаться что-то сделать, но это не всегда так.

Чтобы точно знать, что происходит, вы можете поместить свой шаблон регулярного выражения в блок 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";
  • 0
    Мне придется рассмотреть ваше предложение. Однако как «количество шагов для получения результата» может стать слишком высоким? Реализация с использованием буфера фиксированного размера. Есть ли способ узнать предел?
  • 0
    В библиотеке по-прежнему есть ошибка, связанная с тем, что стандарт не допускает сбоя реализации (с переполнением стека). Он должен regex_error и позволить пользователю восстановиться.
Показать ещё 18 комментариев
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
    Я также заметил, что std::regex e(".*/\\*[\\x00-\\xff]*?\\*/"); тоже работает

Ещё вопросы

Сообщество Overcoder
Наверх
Меню