4

I was under the impression that while dereferencing pointers that don't point to a valid object is UB, simply computing such pointers is fine.

However, if I'm understanding expr.add[4] correctly, that's not the case.

So which of these pointer computations are well-defined?

int a = 42;
int *p = &a;
p;           // valid, and obviously ok
p++;         // invalid, but ok, because one past the end of 'array' containing 1 element?
p++;         // UB ?

How about this case?

int *p = nullptr;
p;                // invalid, and obviously ok (considered one past the end?)
p++;              // one past the end? or UB?
cigien
  • 57,834
  • 11
  • 73
  • 112
  • Outside valid pointers, the only 2 allowed pointers are null pointer and 1 past array end. Your linked section summarizes it clearly, it seems? – rustyx Apr 18 '20 at 21:34
  • @rustyx clearly enough for the 1st case, I *think*. I'm not sure about the second case though. – cigien Apr 18 '20 at 21:36
  • @rustyx See the note (76) right at the bottom of the linked page – Richard Critten Apr 18 '20 at 21:45
  • @RichardCritten That's what my reasoning was for the 1st `p++`. Does that not apply to the 3rd case? – cigien Apr 18 '20 at 21:49
  • @cigien `nullptr` doesn't point to an object so the footnote doesn't apply? – Artyer Apr 18 '20 at 21:53
  • @Artyer yeah, that seems resonable. – cigien Apr 18 '20 at 21:54
  • You have asked which are **allowed**. Not only does the C++ standard not prohibit you from doing these things, it is a voluntary standard you do not have to obey at all. The question you meant to ask is which are **defined** by the standard. – Eric Postpischil Apr 18 '20 at 22:26
  • @EricPostpischil That's true, edited, thanks. – cigien Apr 18 '20 at 22:28
  • 1
    The two cases you call "invalid" are valid. Past-the-end, and null pointers are valid pointers . (see [basic.compound]/3) – M.M Apr 18 '20 at 23:03
  • @M.M Yes, but I feel editing those particular usages now would invalidate that part of the answer below. – cigien Apr 18 '20 at 23:07

1 Answers1

5

In your first example, the first p++ is well-defined, because a non-array is considered a one-length array.

Here's the relevant quote (basic.compound/3.4):

For purposes of pointer arithmetic ([expr.add]) and comparison ([expr.rel], [expr.eq]), a pointer past the end of the last element of an array x of n elements is considered to be equivalent to a pointer to a hypothetical array element n of x and an object of type T that is not an array element is considered to belong to an array with one element of type T.

After p++, p it will point past the last (and only) element of the (hypothetical) array, which is well-defined. It is not "invalid, but ok", as pointers pointing to past the end of an object are not invalid pointers, basic.compound/3.2:

Every value of pointer type is one of the following:

  • [...]

  • a pointer past the end of an object

  • [...]

  • an invalid pointer value.

The second p++ of the first example is UB, because the result will point after the hypothetical (&a)[1] element, which is not defined.

In your second example, p++ is UB, because only 0 can be added to a nullptr (expr.add/4.1):

  • If P evaluates to a null pointer value and J evaluates to 0, the result is a null pointer value.

  • [...]

  • Otherwise, the behavior is undefined.

Community
  • 1
  • 1
geza
  • 28,403
  • 6
  • 61
  • 135