如果算法 std::mismatch 的两个范围的大小不同会产生未定义的行为?

Posted

技术标签:

【中文标题】如果算法 std::mismatch 的两个范围的大小不同会产生未定义的行为?【英文标题】:If the sizes of the two ranges to the algorithm std::mismatch different yields Undefined Behavior? 【发布时间】:2021-09-11 00:11:25 【问题描述】:

我在 Cppreference 上看到了算法 std::mismatch 的可能实现:

template<class InputIt1, class InputIt2>
std::pair<InputIt1, InputIt2>
mismatch(InputIt1 first1, InputIt1 last1, InputIt2 first2)

   while (first1 != last1 && *first1 == *first2) 
      ++first1, ++first2;
   
   return std::make_pair(first1, first2);

因此,如果第一个范围比第二个长,那么在某些时候,第二个范围中的越端迭代器将被取消引用,然后递增,从而产生未定义的行为。对?例如:

std::vector<int> v124, 10, 81, 7, 57;
std::vector<int> v224, 10, 81;
auto p = std::mismatch(v1.cbegin(), v1.cend(), v2.cbegin()); // UB?

所以元素是相等的,直到 v1 位于 7 并且 v2past the last element 所以它被取消引用然后递增,从而导致 UB。

我认为算法也必须在每次迭代时检查第二个范围是否在末尾:

first1 != last1 && first2 != last2

我认为第三和第四个版本是正确的:

  template<class InputIt1, class InputIt2>
  std::pair<InputIt1, InputIt2> mismatch(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2)
  
      while (first1 != last1 && first2 != last2 && *first1 == *first2) 
          ++first1, ++first2;
      
      return std::make_pair(first1, first2);
  

  Fourth version

  template<class InputIt1, class InputIt2, class BinaryPredicate>
  std::pair<InputIt1, InputIt2> mismatch(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, BinaryPredicate p)
  
      while (first1 != last1 && first2 != last2 && p(*first1, *first2)) 
          ++first1, ++first2;
      
      return std::make_pair(first1, first2);
  

这里是cppreference主题的链接:

https://en.cppreference.com/w/cpp/algorithm/mismatch

【问题讨论】:

如果您不确定范围的长度是否相同,那就是 wgat 接受两对开始/结束迭代器的重载。 @ItachiUchiwa 他们采用 4 个迭代器 [first1,last1)[first2,last2) 算法 5、6、7 和 8。还有 "...如果未提供 last2(重载 (1-4) ),它表示 first2 + (last1 - first1)...." 因此,在调用 1 到 4 中的任何一个之前,您必须确保合同为真。因此范围 2 必须至少与范围一样长1. @ItachiUchiwa 您应该使用 4 迭代器版本,除非您确定第一个范围不比第三个参数后面的范围长。如果您不确定,请始终使用 4 迭代器版本。 问题链接到的文档回答了问题。阅读返回值下的文字。 "我认为第三和第四个版本是对的:" -- 你说的“对”是什么意思?第三和第四个版本分别适用于实现重载 5 和 7,但不适用于实现其他重载。特别是对于重载 1,它们不可能正确,因为重载 1 需要三个参数,第三个版本需要四个参数,第四个版本需要五个参数。看起来你的“正确”概念会更好地针对声明而不是实现。 【参考方案1】:

是的,假定旧重载的范围相同。来自MSVC documentation:

在 C++14 代码中使用双范围重载,因为如果第二个范围比第一个范围长,则仅对第二个范围采用单个迭代器的重载将不会检测到差异,并且会导致未定义的行为,如果第二个范围比第一个范围短

使用双范围算法的性能损失最小(每个循环一次额外比较),并且对于随机访问迭代器,双范围 std::equals 和 std::is_permutation 需要在如果长度不同,则为常数时间。如果你有兴趣,在 CPPcon 上有一个谈话 by MSVC standard library implementer Stephan Lavavej from 2014 讨论这些变化,以及数字的“inner_product”函数和“transform”的二进制版本被遗漏了(并且仍然只有“range-and-”半'超载)。

如果双范围算法可用,则强烈推荐它们。仅当您知道自己在做什么并且绝对需要性能时才使用其他重载(然后通常仅使用非随机访问迭代器,因为编译器通常会通过检查来优化随机迭代器上的双范围重载(例如 is_permutation)长度,然后调用范围半重载)。

【讨论】:

以上是关于如果算法 std::mismatch 的两个范围的大小不同会产生未定义的行为?的主要内容,如果未能解决你的问题,请参考以下文章

STL学习之mismatch();

二分算法(以 数的范围 为例)

[算法必须死 第3期] 旋转数组的二分查找

算法-排序-1.冒泡排序/2.选择排序/3.插入排序

有关数组的算法题

查找两个整数范围之间的重叠区域