c++reverse-iterator

Reverse iterators and negative strided iterators in C++, using one before beginning as sentinel


In Another way of looking at C++ reverse iterators Raymond Chen wrote:

... a quirk of the C++ language: You are allowed to have a pointer "one past the end" of a collection, but you are not allowed to have a pointer "one before the beginning" of a collection.

I understand that it probably means "undefined behavior" and that is pretty much a conversation ender. But I am curious what is the worst that can happen in a realistic system if one ignores this rule. A segmentation fault? an overflow of pointer arithmetic? unnecessary paginations?

Remember that the pointer "before" the beginning (like "end") is not supposed to be referenced either, the problem seem to have the pointer just trying to point to it.

The only situation I can imagine it can be a problem is with a system where memory location "0" is valid. But even then, if that is so there are bigger problems, (e.g. nullptr would be problematic in itself too and wrapping around might still work by convention I guess.)

I am not questioning the implementation of reverse_iterator with an off-by-one special code. The question occurred to me because if you have a generic implementation of "strided" iterator would require a special logic for negative strides and that has a cost (in code and at runtime).

Arbitrary strides, including negative ones could be naturally appearing with multidimensional arrays. I have a multidimensional array library in which I always allowed negative strides in principle, but now I realized that they should have a special code (different from the positive case) if I allowed them at all and I don't want to expose undefined behavior.


Solution

  • But I am curious what is the worst that can happen in a realistic system if one ignores this rule. A segmentation fault? an overflow of pointer arithmetic? unnecessary paginations?

    Wasted space.

    To be able to form the address "one before" you need to have the space available. For example, if you have a 1k struct, it must start at least 1k from the beginning of memory. On a small device with limited memory, or an x86 with segmented memory, that can be tricky.

    To form a "one past" pointer/iterator you only need one byte beyond the end. As it cannot be dereferenced anyway, there is no need to have space for an entire object, just the possibility to form the address.


    From the comments:

    At the time the rules were defined, we had real CPUs with dedicated address registers that validated the address against the segment size on register load. That made sure that a -1 address would trap...

    https://en.wikipedia.org/wiki/Motorola_68000#Architecture

    This ruled out the case of having "one before" wrapping around to the end of the segment. Because if the segment is small, the resulting end might not be there.