18

According to C++ standard (3.7.3.2/4) using (not only dereferencing, but also copying, casting, whatever else) an invalid pointer is undefined behavior (in case of doubt also see this question). Now the typical code to traverse an STL containter looks like this:

std::vector<int> toTraverse;
//populate the vector
for( std::vector<int>::iterator it = toTraverse.begin(); it != toTraverse.end(); ++it ) {
    //process( *it );
}

std::vector::end() is an iterator onto the hypothetic element beyond the last element of the containter. There's no element there, therefore using a pointer through that iterator is undefined behavior.

Now how does the != end() work then? I mean in order to do the comparison an iterator needs to be constructed wrapping an invalid address and then that invalid address will have to be used in a comparison which again is undefined behavior. Is such comparison legal and why?

Community
  • 1
  • 1
sharptooth
  • 167,383
  • 100
  • 513
  • 979
  • 3.7.3.2/4 doesn't say that copying and casting an invalid pointer is UB. I believe that your interpretation is too wide. – Kirill V. Lyadvinsky Apr 28 '10 at 10:10
  • @Kirill V. Lyadvinsky: Maybe, but that's the core of the linked question where the consensus is that casting and assigning invalid pointers is UB. – sharptooth Apr 28 '10 at 10:16

8 Answers8

26

The only requirement for end() is that ++(--end()) == end(). The end() could simply be a special state the iterator is in. There is no reason the end() iterator has to correspond to a pointer of any kind.

Besides, even if it were a pointer, comparing two pointers doesn't require any sort of dereference anyway. Consider the following:

char[5] a = {'a', 'b', 'c', 'd', 'e'};
char* end = a+5;
for (char* it = a; it != a+5; ++it);

That code will work just fine, and it mirrors your vector code.

Nick Lewis
  • 4,150
  • 1
  • 21
  • 22
  • That says is better than my answer. +1 from me. – sbi Apr 28 '10 at 09:49
  • @Nick Lewis: I won't argue against other points, but the Standard says that even using an invalid pointer is UB, so `char* end = a+5;` is UB. – sharptooth Apr 28 '10 at 09:52
  • 14
    @sharptooth: One past end of the array is **not** an invalid pointer. – UncleBens Apr 28 '10 at 09:57
  • @sharptooth: Just because the standard says so. – sbi Apr 28 '10 at 10:04
  • Nick, I think you mean `++(--end()) == end()`, because for example `end()-- == end()` also evaluates to true since the postfix form returns the unmodified original. – fredoverflow Apr 28 '10 at 10:55
  • @FredOverflow: You're absolutely correct, I've fixed the code. It's actually incorrect for another reason as well: `end()--` returns `end()`, so it then tries to increment `end()` which is not allowed. – Nick Lewis Apr 28 '10 at 21:44
  • `++--end()` is UB if the container is empty, so that cannot be a requirement – M.M Mar 01 '18 at 22:06
11

You're right that an invalid pointer can't be used, but you're wrong that a pointer to an element one past the last element in an array is an invalid pointer - it's valid.

The C standard, section 6.5.6.8 says that it's well defined and valid:

...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...

but cannot be dereferenced:

...if the result points one past the last element of the array object, it shall not be used as the operand of a unary * operator that is evaluated...

JoeG
  • 12,994
  • 1
  • 38
  • 63
  • 3
    The last quote is not true about C++. If you know that after the array another object of the element type resides (as in a multidim array) you *can* dereference it. – Johannes Schaub - litb Apr 28 '10 at 11:00
  • Do you have a reference for that and is it just valid in C++ but not C? – JoeG Apr 28 '10 at 12:10
  • 3
    it's valid (not UB) in C++ and UB in C, yes. But only if there is indeed an object at that position. See `5.7/5` and `3.9.2/3`. – Johannes Schaub - litb Apr 28 '10 at 16:19
  • 2
    Down vote because C and C++ are not the same, as mentioned above. It's somewhat misleading. – FrankHB Mar 18 '16 at 15:36
  • This doesn't seem to address the question. Iterators are usually of class type, not raw pointers. Also the C standard quotes are irrelevant to C++. The C++ standard doesn't say those things. – M.M Mar 01 '18 at 22:05
5

One past the end is not an invalid value (neither with regular arrays or iterators). You can't dereference it but it can be used for comparisons.

std::vector<X>::iterator it;

This is a singular iterator. You can only assign a valid iterator to it.

std::vector<X>::iterator it = vec.end();

This is a perfectly valid iterator. You can't dereference it but you can use it for comparisons and decrement it (assuming the container has a sufficient size).

UncleBens
  • 40,819
  • 6
  • 57
  • 90
  • Why is "one past the end" valid exactly? – sharptooth Apr 28 '10 at 10:02
  • Section 6.5.6.8 of the C standard explicitly allows it. – JoeG Apr 28 '10 at 10:03
  • @sharptooth: The standard talks about the validity of comparing the address of one past the end of arrays in many places. Imagine if this weren't the case - you would not be able to use != to detect the end of arrays when looping, copying, etc which would be very tedious. It is invalid to dereference one past the end though. – markh44 Apr 28 '10 at 10:18
  • I think another good reason why *one past the end* was defined to be valid is that this is greatly simplifying pointer-arithmetic with arrays. If it wasn't valid, you'd have to `last-first+1` to get the size of an array. Just a guess though. – Björn Pollex Apr 28 '10 at 11:00
  • @BjörnPollex Also, that would not work with empty dynamically arrays, where there is simply no "last". – curiousguy Sep 29 '11 at 21:11
3

Huh? There's no rule that says that iterators need to be implemented using nothing but a pointer.

It could have a boolean flag in there, which gets set when the increment operation sees that it passes the end of the valid data, for instance.

unwind
  • 391,730
  • 64
  • 469
  • 606
2

The implementation of a standard library's container's end() iterator is, well, implementation-defined, so the implementation can play tricks it knows the platform to support.
If you implemented your own iterators, you can do whatever you want - so long as it is standard-conform. For example, your iterator, if storing a pointer, could store a NULL pointer to indicate an end iterator. Or it could contain a boolean flag or whatnot.

sbi
  • 219,715
  • 46
  • 258
  • 445
  • 1
    No tricks required - one past the last element is a valid pointer that can't be dereferenced. – JoeG Apr 28 '10 at 10:02
  • 1
    @Joe: I didn't say that tricks were _required_. I said the implementation _can_ play tricks. (And try to have a one-past-the-end iterator for a list.) So I'm not sure what the down-vote is for. – sbi Apr 28 '10 at 10:03
  • The question is about why a pointer past the end of an array can be legally used, your answer implies that `end()` is only valid because of implementation defined tricks. – JoeG Apr 28 '10 at 10:07
  • @Joe: I read the question as how implementing `end()` could be legal __for any container__ (and _not_ just for `std::vector`). I didn't even consider `std::vector` to be worth discussing, because I (wrongly) assumed sharptooth to know that a pointer one-past-the-array is allowed. Yet even if it wasn't, an implementation could always allocate room for one more object and use its address for `end()` - which is why I believe the one-past-the-array rule is there _just for arrays__, not for STL containers. Therefor my answer is about any STL container, not `std::vector`. – sbi Apr 28 '10 at 10:19
2

I answer here since other answers are now out-of-date; nevertheless, they were not quite right to the question.

First, C++14 has changed the rules mentioned in the question. Indirection through an invalid pointer value or passing an invalid pointer value to a deallocation function are still undefined, but other operations are now implemenatation-defined, see Documentation of "invalid pointer value" conversion in C++ implementations.

Second, words matter. You can't bypass the definitions while applying the rules. The key point here is the definition of "invalid". For iterators, this is defined in [iterator.requirements]. Though pointers are iterators, meanings of "invalid" to them are subtly different. Rules for pointers render "invalid" as "don't indirect through invalid value", which is a special case of "not dereferenceable" to iterators; however, "not deferenceable" is not implying "invalid" for iterators. "Invalid" is explicitly defined as "may be singular", while "singular" value is defined as "not associated with any sequence" (in the same paragraph of definition of "dereferenceable"). That paragraph even explicitly defined "past-the-end values".

From the text of the standard in [iterator.requirements], it is clear that:

  • Past-the-end values are not assumed to be dereferenceable (at least by the standard library), as the standard states.
  • Dereferenceable values are not singular, since they are associated with sequence.
  • Past-the-end values are not singular, since they are associated with sequence.
  • An iterator is not invalid if it is definitely not singular (by negation on definition of "invalid iterator"). In other words, if an iterator is associated to a sequence, it is not invalid.

Value of end() is a past-the-end value, which is associated with a sequence before it is invalidated. So it is actually valid by definition. Even with misconception on "invalid" literally, the rules of pointers are not applicable here.

The rules allowing == comparison on such values are in input iterator requirements, which is inherited by some other category of iterators (forward, bidirectional, etc). More specifically, valid iterators are required to be comparable in the domain of the iterator in such way (==). Further, forward iterator requirements specifies the domain is over the underlying sequence. And container requirements specifies the iterator and const_iterator member types in any iterator category meets forward iterator requirements. Thus, == on end() and iterator over same container is required to be well-defined. As a standard container, vector<int> also obey the requirements. That's the whole story.

Third, even when end() is a pointer value (this is likely to happen with optimized implementation of iterator of vector instance), the rules in the question are still not applicable. The reason is mentioned above (and in some other answers): "invalid" is concerned with *(indirect through), not comparison. One-past-end value is explicitly allowed to be compared in specified ways by the standard. Also note ISO C++ is not ISO C, they also subtly mismatches (e.g. for < on pointer values not in the same array, unspecified vs. undefined), though they have similar rules here.

sehe
  • 374,641
  • 47
  • 450
  • 633
FrankHB
  • 2,297
  • 23
  • 19
1

Simple. Iterators aren't (necessarily) pointers.

They have some similarities (i.e. you can dereference them), but that's about it.

Roddy
  • 66,617
  • 42
  • 165
  • 277
0

Besides what was already said (iterators need not be pointers), I'd like to point out the rule you cite

According to C++ standard (3.7.3.2/4) using (not only dereferencing, but also copying, casting, whatever else) an invalid pointer is undefined behavior

wouldn't apply to end() iterator anyway. Basically, when you have an array, all the pointers to its elements, plus one pointer past-the-end, plus one pointer before the start of the array, are valid. That means:

int arr[5];
int *p=0;
p==arr+4; // OK
p==arr+5; // past-the-end, but OK
p==arr-1; // also OK
p==arr+123456; // not OK, according to your rule
jpalecek
  • 47,058
  • 7
  • 102
  • 144
  • Why specifically are "before the first" and "beyond the last" pointers valid? – sharptooth Apr 28 '10 at 10:01
  • 6
    `p==arr-1;` invokes undefined behaviour ("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.") – JoeG Apr 28 '10 at 10:04
  • _"plus one pointer past-the-end"_ is valid, if poorly worded. _"plus one pointer before the start of the array"_ is not valid. The Standard does not make a special allowance for 1-before-start like it does for 1-past-end. – underscore_d Apr 05 '18 at 10:35