Объединение регулярных выражений и диапазонов вызывает проблемы с памятью

Я хотел построить представление обо всех подсовпадениях regex в text. Вот два способа определить такое представление:

    char const text[] = "The IP addresses are: 192.168.0.25 and 127.0.0.1";
    std::regex regex{R"((\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3}))"};

    auto sub_matches_view = 
        std::ranges::subrange(
            std::cregex_iterator{std::ranges::begin(text), std::ranges::end(text), regex},
            std::cregex_iterator{}
        ) |
        std::views::join;

    auto sub_matches_sv_view = 
        std::ranges::subrange(
            std::cregex_iterator{std::ranges::begin(text), std::ranges::end(text), regex},
            std::cregex_iterator{}
        ) |
        std::views::join |
        std::views::transform([](std::csub_match const& sub_match) -> std::string_view { return {sub_match.first, sub_match.second}; });
  • Тип значения sub_matches_viewstd::csub_match. Он создается путем предварительного создания представления объектов std::cmatch (через итератор регулярных выражений), и, поскольку каждый std::cmatch представляет собой диапазон объектов std::csub_match, он выравнивается с помощью std::views::join.
  • Тип значения sub_matches_sv_viewstd::string_view. Он идентичен sub_matches_view, за исключением того, что он также заключает каждый элемент sub_matches_view в std::string_view.

Вот пример использования вышеуказанных диапазонов:

for(auto const& sub_match : sub_matches_view) {
    std::cout << std::string_view{sub_match.first, sub_match.second} << std::endl; // #1
}

for(auto const& sv : sub_matches_sv_view) {
    std::cout << sv << std::endl; // #2
}

Петля #1 работает без проблем - результаты распечатки правильные. Однако Цикл #2 вызывает проблемы с использованием кучи после освобождения в соответствии с Address Sanitizer. На самом деле, простое повторение sub_matches_sv_view без доступа к элементам также вызывает эту проблему. Здесь — это код в Compiler Explorer, а также выходные данные Address Sanitizer.

У меня нет идей относительно того, где моя ошибка. text и regex никогда не выходят за рамки, я не вижу никаких итераторов, к которым можно было бы получить доступ за пределами их жизни. Объект std::csub_match содержит итераторы (.first, .second) в text, поэтому я не думаю, что он должен оставаться живым после создания std::string_view в std::views::transform.

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

godbolt.org/z/qoTbTYcxG
Barry 11.05.2022 15:49

@Barry Большое спасибо, я пытался несколько раз, но не смог скомпилировать его достаточно быстро (возможно, потому, что я использовал немного другие флаги). На моей машине это занимает около 30 секунд. Я отредактирую вашу ссылку в вопросе.

Ice1357 11.05.2022 15:52

Кстати, в libc++ такой проблемы не было. godbolt.org/z/a7M3jd8ef

康桓瑋 11.05.2022 16:32

Некоторая ошибка в трансформации: godbolt.org/z/q4P1x5qTb без transform работает.

Marek R 11.05.2022 16:37

Я не думаю, что это баг transform_view, я думаю, проблема скорее в <regex>.

康桓瑋 11.05.2022 16:54

@MarekR Это определенно не ошибка transform.

Barry 11.05.2022 16:54

Цикл заметок @Barry, который я добавил в пример, который пропускает только std::views::transform. Это работает нормально.

Marek R 11.05.2022 16:58

У @MarekR OP была такая же петля? Вы не добавляете никакой информации?

Barry 11.05.2022 16:59
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
6
8
96
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Проблема в std::regex_iterator и в том, что он тайник.


Этот тип в основном выглядит так:

class regex_iterator {
    vector<match> matches;

public:
    auto operator*() const -> vector<match> const& { return matches; }
};

Это означает, например, что, хотя тип ссылки этого итератора — T const&, если у вас есть две копии одного и того же итератора, они фактически дадут вам ссылки на разные объекты.

Теперь join_view<R>::iterator в основном выглядит так:

class iterator {
    // the iterator into the range we're joining
    iterator_t<R> outer;

    // an iterator into *outer that we're iterating over
    iterator_t<range_reference_t<R>> inner;
};

Что для regex_iterator примерно выглядит так:

class iterator {
    // the regex matches
    vector<match> outer;

    // the current match
    match* inner;
};

Что произойдет, если вы скопируете этот итератор? Копия inner по-прежнему относится к оригиналу outer! На самом деле они не являются независимыми, как вы ожидаете. Это означает, что если оригинал выходит за рамки, у нас есть оборванный итератор!

Вот что вы видите здесь: transform_view заканчивается копированием итератора (что, безусловно, разрешено делать), и теперь у вас есть оборванный итератор (вместо этого перемещается реализация libc++, поэтому он работает в таком случае как 康桓瑋указал) . Но мы можем воспроизвести ту же проблему без transform, если мы скопируем итератор и уничтожим оригинал. Например:

#include <ranges>
#include <regex>
#include <iostream>
#include <optional>

int main() {
    std::string_view text = "The IP addresses are: 192.168.0.25 and 127.0.0.1";
    std::regex regex{R"((\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3}))"};

    auto a =  std::ranges::subrange(
            std::cregex_iterator(std::ranges::begin(text), std::ranges::end(text), regex),
            std::cregex_iterator{}
        );

    auto b = a | std::views::join;

    std::optional i = b.begin();
    std::cout << std::string_view((*i)->first, (*i)->second) << '\n'; // fine

    auto j = *i;
    i.reset();
    std::cout << std::string_view(j->first, j->second) << '\n'; // boom
}

Я не уверен, как будет выглядеть решение этой проблемы, но причина в std::regex_iterator, а не в views::join или views::transform.

Другие вопросы по теме