结合正则表达式和范围会导致内存问题
Combining regex and ranges causes memory issues
我想对 text
中 regex
的所有子匹配构建一个视图。以下是定义此类视图的两种方法:
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_view
的值类型是 std::csub_match
。它是通过首先构建 std::cmatch
对象的视图(通过正则表达式迭代器)创建的,并且由于每个 std::cmatch
都是 std::csub_match
对象的范围,因此它被 std::views::join
展平.
sub_matches_sv_view
的值类型是 std::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
}
Loop #1
工作没有问题 - 打印结果是正确的。但是,根据 Address Sanitizer,loop #2
会导致堆释放后使用问题。 事实上,只是循环 sub_matches_sv_view
而根本不访问元素也会导致这个问题。 Here 是 Compiler Explorer 上的代码,也是 Address Sanitizer 的输出。
我不知道我的错误在哪里。 text
和 regex
永远不会超出范围,我没有看到任何可能在其生命周期之外访问的迭代器。 std::csub_match
对象将迭代器 (.first
、.second
) 保存到 text
中,所以我认为在构造 std::string_view
之后它不需要自己保持活动状态std::views::transform
.
我知道还有许多其他方法可以迭代正则表达式匹配,但我对导致我的程序内存错误的原因特别感兴趣,我不需要解决此问题的方法。
问题是 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++ 的实现改为移动,这就是为什么它恰好有效 in this case as 康桓瑋 )。但是我们可以在没有 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
.
我想对 text
中 regex
的所有子匹配构建一个视图。以下是定义此类视图的两种方法:
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_view
的值类型是std::csub_match
。它是通过首先构建std::cmatch
对象的视图(通过正则表达式迭代器)创建的,并且由于每个std::cmatch
都是std::csub_match
对象的范围,因此它被std::views::join
展平.sub_matches_sv_view
的值类型是std::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
}
Loop #1
工作没有问题 - 打印结果是正确的。但是,根据 Address Sanitizer,loop #2
会导致堆释放后使用问题。 事实上,只是循环 sub_matches_sv_view
而根本不访问元素也会导致这个问题。 Here 是 Compiler Explorer 上的代码,也是 Address Sanitizer 的输出。
我不知道我的错误在哪里。 text
和 regex
永远不会超出范围,我没有看到任何可能在其生命周期之外访问的迭代器。 std::csub_match
对象将迭代器 (.first
、.second
) 保存到 text
中,所以我认为在构造 std::string_view
之后它不需要自己保持活动状态std::views::transform
.
我知道还有许多其他方法可以迭代正则表达式匹配,但我对导致我的程序内存错误的原因特别感兴趣,我不需要解决此问题的方法。
问题是 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++ 的实现改为移动,这就是为什么它恰好有效 in this case as 康桓瑋 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
.