Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is clamping on iterators valid

I found the following in actual production code. My suspicion is that it actually has undefined behavior into it, however, I couldn't find the related info on cppreference. Can you confirm this is UB or valid code and why this is UB/valid (preferably with a quote of the standard)?

#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

As you can see begin + 10 will create an iterator that's out of range of the std::vector. However, that iterator ain't being used, as it is clamped using std::min.

like image 612
JVApen Avatar asked Jul 03 '20 07:07

JVApen


People also ask

What invalidates an iterator?

An Iterator becomes invalidate when the container it points to changes its shape internally i.e. move elements from one location to another and the initial iterator still points to old invalid location. Iterator invalidation in vector happens when, An element is inserted to vector at any location.

Is iterator valid after erase?

Every iterator and reference after the point of erasing is invalidated. Only the iterators and references to the erased element is invalidated.

How can we avoid iterator invalidation?

To avoid invalidation of references to elements you can use a std::deque if you do not insert or erase in the middle. To avoid invalidation of iterators you can use a std::list.

Why are my iterators not passing the end of the container?

Each iterator that was pointing at some element that has been erased from the container becomes invalid, but it doesn't pass the end of the container! This means that an iterator that was pointing at some element that has been erased from the container cannot be compared to container.end ().

Why are my iterators invalid in C++?

Iterator Invalidation in C++. One should be careful while using iterators in C++. When we iterate over our container using iterators then it may happen that iterator gets invalidated. This may be due to change in the shape and size of the container while iterating.

Can you determine if an iterator is (safely) dereferenced?

I assume you mean "is an iterator valid," that it hasn't been invalidated due to changes to the container (e.g., inserting/erasing to/from a vector). In that case, no, you cannot determine if an iterator is (safely) dereferencable. Show activity on this post.

What is the purpose of iterators in C++?

An iterator in C++ serves the following major purposes: The primary objective of an iterator is to access the STL container elements and perform certain operations on them. The internal structure of a container does not matter, since the iterators provide common usage for all of them. Iterator algorithms are not dependent on the container type.


2 Answers

The operational semantics of operator+(n), for a random access iterator is this [random.access.iterators], Table 99 *:

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

And for ++r the precondition is [input.iterators], Table 95 *:

Preconditions: r is dereferenceable.

With begin() + n this precondition will not be satisfied starting from some value of m if n is greater than the size of the container. After begin + 10; you already have UB, and the rest of the code is irrelevant.

GCC standard library sanitizer (compile with -D_GLIBCXX_DEBUG) will give you the following error:

/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 (March 2017 post-Kona working draft/C++17 DIS)
like image 192
Evg Avatar answered Oct 19 '22 23:10

Evg


Well, defining an iterator that is out-of-range is UB according to the Standard §5/5.7:

When an expression that has integral type is added to or subtracted from a pointer, the result has the typeof the pointer operand. If the pointer operand points to an element of an array object, and the array islarge enough, the result points to an element offset from the original element such that the difference ofthe subscripts of the resulting and original array elements equals the integral expression. In other words, if the expression points to the i-th element of an array object, the expressions (P)+N (equivalently, N+(P)) and (P)-N (where N has the valuen) point to, respectively, the i+n-th and i−n-th elements of the arrayobject, provided they exist. Moreover, if the expression P points to the last element of an array object,the expression (P)+1 points one past the last element of the array object, and if the expressionQpointsone past the last element of an array object, the expression (Q)-1 points to the last element of the arrayobject. If both the pointer operand and the result point to elements of the same array object, or one past the last element of the array object, the evaluation shall not produce an overflow; otherwise, the behavior is undefined

You can verify this if you turn on iterator debugging for 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
    }
like image 40
A.K. Avatar answered Oct 20 '22 00:10

A.K.