【问题标题】:Is clamping on iterators valid对迭代器的钳位是否有效
【发布时间】:2020-10-23 21:50:45
【问题描述】:

我在实际的生产代码中发现了以下内容。 我怀疑它实际上具有未定义的行为,但是,我在 cppreference 上找不到相关信息。您能否确认这是 UB 或有效代码以及为什么这是 UB/有效(最好带有标准的引用)?

#include <vector>

int main(int, char **)
{
    auto v = std::vector<int>({1,2,3,4,5});
    auto begin = v.begin();
    auto outOfRange = begin + 10;
    auto end = v.end();
    auto clamped = std::min(outOfRange, end);
    return (clamped == end) ? 0 : 42;
}

Code on Compiler Explorer

如您所见,begin + 10 将创建一个超出std::vector 范围的迭代器。 但是,没有使用该迭代器,因为它使用std::min 进行了限制。

【问题讨论】:

  • 我认为一旦你形成了一个超出结尾的迭代器,你就有了 UB,无论它是否被取消引用。这与指针相同,这对于迭代器来说很常见。
  • @underscore_d 所有这些答案都说它是 UB,但没有解释原因
  • 那么我希望有人应该在我们已经提出的问题上发布一个更好的答案!
  • 我现在投票关闭另一个作为此副本的副本,因为 Evg 的回答最终引用了标准。这应该是公认的答案。

标签: c++ stl iterator language-lawyer


【解决方案1】:

operator+(n) 的操作语义,对于随机访问迭代器是这个[random.access.iterators], Table 99 *:

difference_­type m = n;
if (m >= 0)
    while (m--)
        ++r;
else
    while (m++)
        --r;
return r;

对于++r,前提条件是[input.iterators], Table 95 *:

前提条件:r 是可取消引用的。

对于begin() + n,如果n 大于容器的大小,则从m 的某个值开始将不满足此前提条件。 begin + 10;之后你已经有UB了,剩下的代码就无关紧要了。

GCC 标准库清理程序(使用 -D_GLIBCXX_DEBUG 编译)会给您以下错误:

/usr/include/c++/10/debug/safe_iterator.h:885:
In function:
    __gnu_debug::_Safe_iterator<__gnu_cxx::__normal_iterator<int*, 
    std::__cxx1998::vector<int, std::allocator<int> > >, 
    std::__debug::vector<int>, std::random_access_iterator_tag>::_Self 
    __gnu_debug::operator+(const _Self&, 
    __gnu_debug::_Safe_iterator<__gnu_cxx::__normal_iterator<int*, 
    std::__cxx1998::vector<int, std::allocator<int> > >, 
    std::__debug::vector<int>, 
    std::random_access_iterator_tag>::difference_type)

Error: attempt to advance a dereferenceable (start-of-sequence) iterator 10 
steps, which falls outside its valid range.

Objects involved in the operation:
    iterator @ 0x0x7fffffffb900 {
      type = __gnu_cxx::__normal_iterator<int*, std::__cxx1998::vector<int, std::allocator<int> > > (mutable iterator);
      state = dereferenceable (start-of-sequence);
      references sequence with type 'std::__debug::vector<int, std::allocator<int> >' @ 0x0x7fffffffb8c0
    }

  • N4659(2017 年 3 月后 Kona 工作草案/C++17 DIS)

【讨论】:

    【解决方案2】:

    好吧,根据标准§5/5.7定义一个超出范围的迭代器是UB:

    当具有整数类型的表达式被添加或减去时 从一个指针,结果具有指针操作数的类型。如果 指针操作数指向数组对象的一个​​元素,而数组 足够大,结果指向一个元素偏移量 原始元素使得下标的差异 结果和原始数组元素等于积分表达式。 换句话说,如果表达式指向一个 i-th 元素 数组对象,表达式(P)+N(等价于N+(P))(P)-N(其中N 的值为n)分别指向 数组对象的i+n-thi−n-th 元素,前提是它们 存在。此外,如果表达式P 指向 一个数组对象,表达式(P)+1 指向最后一个 数组对象的元素,如果表达式 Qpointsone 超过 数组对象的最后一个元素,表达式(Q)-1 指向 数组对象的最后一个元素。如果指针操作数和 结果指向同一数组对象的元素,或过去的一个 数组对象的最后一个元素,评估不应产生 溢出;否则,行为未定义

    如果你打开gcc的迭代器调试,你可以验证这一点

    # g++ main.cpp -D_GLIBCXX_DEBUG -o main
    # ./main
    C:/mingw-w64/i686-8.1.0-win32-dwarf-rt_v6-rev0/mingw32/lib/gcc/i686-w64-mingw32/8.1.0/include/c++/debug/safe_iterator.h:374:
    Error: attempt to advance a dereferenceable (start-of-sequence) iterator 10
    steps, which falls outside its valid range.
    
    Objects involved in the operation:
        iterator @ 0x0061FE3C {
          type = __gnu_debug::_Safe_iterator<__gnu_cxx::__normal_iterator<int*, std::__cxx1998::vector<int, std::allocator<int> > >, std::__debug::vector<int, std::allocator<int> > > (mutable iterator);
          state = dereferenceable (start-of-sequence);
          references sequence with type 'std::__debug::vector<int, std::allocator<int> >' @ 0x0061FE50
        }
    

    【讨论】:

    • 需要注意的是,迭代器一般不是指针。甚至 std::vector 迭代器也可以使用用户定义的类型 (class) 来实现。
    • 另一个答案更好,因为它明确(取消)定义了迭代器的行为,而您的只是提到了指针并假设(相当公平地)它们是迭代器的代表。但是,这不一定是正确的,我认为也不足以最终回答有关迭代器的具体问题。
    • 你们俩都是对的。我假设这足以将其声明为 UB,因为迭代器指向的值存储在指针中,根据:en.cppreference.com/w/cpp/iterator/iterator
    猜你喜欢
    • 2011-01-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-01-21
    • 2011-05-06
    • 2010-09-30
    • 1970-01-01
    相关资源
    最近更新 更多