12

Try as I might, the closest answer I've seen is this, with two completely opposing answers(!)

The question is simple, is this legal?

auto p = reinterpret_cast<int*>(0xbadface);
*p;  // legal?

My take on the matter

  1. Casting integer to pointer: no restrictions on what may be casted
  2. Indirection: only states the result is a lvalue.
  3. Lifetimes: only states what can't be done on objects, there is no object here
  4. Expression statements: *p is a discarded value expression
  5. Discarded value expressions: no lvalue-to-rvalue conversion occurs
  6. Undefined-ness of lvalues: aka strict aliasing rule, only if the lvalue is converted to a rvalue

So I conclude there is nothing explicitly saying this is undefined behaviour. Yet I distinctively remember that some platforms trap on indirection for invalid pointers. What went wrong with my reasoning?

Passer By
  • 19,325
  • 6
  • 49
  • 96
  • Do you really think that could be legal? –  Nov 10 '17 at 17:15
  • @manni66 I don't. But I can't prove that it isn't. – Passer By Nov 10 '17 at 17:15
  • Nice. Above my paygrade though. `&*p` is *definitely* legal. – Bathsheba Nov 10 '17 at 17:20
  • Is `*p;` different from `auto x = *p;` with regards to the requirements for `p`? That is, can there be a case where, for a pointer `int* p`, `*p;` is legal but `auto x = *p;` is not? – François Andrieux Nov 10 '17 at 17:23
  • If int object exists at `0xbadface`, it should be ok, else strict aliasing should be broken. – Jarod42 Nov 10 '17 at 17:24
  • `v[10]` in your comment is _definetly_ illegal, no matter the answer to this question. – Revolver_Ocelot Nov 10 '17 at 17:27
  • @FrançoisAndrieux `auto x = *p;` requires an lvalue-to-rvalue conversion on `*p`, `*p;` doesn't – Passer By Nov 10 '17 at 17:32
  • @Jarod42 The strict aliasing rule in the standard is worded in terms of _"access the stored value of an object through a glvalue"_, from [[basic.lval]](http://eel.is/c++draft/basic.lval#8). No stored value is accessed, we don't have strict aliasing violations. – Passer By Nov 10 '17 at 17:34
  • @Passer The compiler is allowed to throw that away as a NOP, no? Would you expect to have that side effects with let's say pointer like types? – user0042 Nov 10 '17 at 17:35
  • @Revolver_Ocelot That requires some more thinking through, it was faulty, but I suspect its not simple – Passer By Nov 10 '17 at 17:35
  • What is your source for legal? It should be illegal in all cases I can think of (except for when there's an int there). The compiler is allowed to emit code that reads this address on*p, which might cause e.g. segfault. – lorro Nov 10 '17 at 17:40
  • @lorro The legalness is in the steps provided in the question. Nothing says its illegal, so it isn't, or so I assume. – Passer By Nov 10 '17 at 17:41
  • @user0042 The compiler would most certainly just issue a NOP, the only way it can have side effects is if it is UB, so the compiler is free of all responsibility. But we only __guess__ it is UB because conceptually, it might trap. – Passer By Nov 10 '17 at 17:42
  • 1
    @Passer _"the only way it can have side effects is if it is UB ..."_ you caught my thought :-) – user0042 Nov 10 '17 at 17:44
  • It worth noting, that if standard does not define behavior for something, it is undefined by... not being defined. The problem is proving, that no part of standard directly or indirectly defines it. – Revolver_Ocelot Nov 10 '17 at 17:46
  • 1
    related: https://stackoverflow.com/questions/35711301/is-it-undefined-behavior-in-c-to-dereference-an-invalid-pointer-but-not-use-th – lorro Nov 10 '17 at 17:48
  • @Revolver_Ocelot But pointer indirection is clearly defined. If nothing says it is an exception, it isn't. – Passer By Nov 10 '17 at 17:48
  • 2
    From standard: _"result is an lvalue referring to the object or function to which the expression points"_. Your pointer does not point to any object, so this line does not apply, and there is nothing else defining the result in that paragraph. – Revolver_Ocelot Nov 10 '17 at 17:51
  • @lorro I'm not sure if it is the same. Simply _reading_ the uninitialized pointer is UB. – Passer By Nov 10 '17 at 17:51
  • @Revolver_Ocelot By that same reasoning, no pointers to objects not within its lifetimes may be dereferenced. Yet [basic.life] says it is allowed. – Passer By Nov 10 '17 at 17:53
  • @PasserBy : read the accepted answer, 1st paragraph – lorro Nov 10 '17 at 17:55
  • @lorro Forgive me for being picky, the first paragraph isn't based on the standard. It is held only as a reason in my question for believing it should be UB, but by no means, a proof – Passer By Nov 10 '17 at 17:56
  • It is an exception or clarification to the general rule. I do not see problem with that. If there any such exception related to discarding result of dereferenceing pointer to nothing _anywhere_ in standard, this behavior is defined, else - undefined. – Revolver_Ocelot Nov 10 '17 at 17:57
  • @Revolver_Ocelot To clarify, what you're saying is, the indirection clause says it's UB, but the lifetime clause says there are exceptions? I'm not sure if that is exactly what it means. – Passer By Nov 10 '17 at 17:59
  • Well, C+++ standard is not exactly easiest to read. Parts of rules related to some behavior might be found in several places. ( I tried to figure out how overload resolution works from standard wording once. It was a mistake ) – Revolver_Ocelot Nov 10 '17 at 18:04
  • @Revolver_Ocelot Write an answer, I can't judge its validity, we can then wait for input from others. Hopefully you can also settle the linked question. – Passer By Nov 10 '17 at 18:06
  • Also related [Does the standard mandate an lvalue-to-rvalue conversion of the pointer variable when applying indirection?](https://stackoverflow.com/q/21053273/1708801) – Shafik Yaghmour Nov 10 '17 at 20:50
  • 1
    Also related [twitter thread 1](https://twitter.com/johnregehr/status/925798552139218944) and [twitter thread 2](https://twitter.com/trap0xf/status/926551096566280192) – Shafik Yaghmour Nov 10 '17 at 21:08
  • The accepted answer is correct now, but the linked question and accepted answer for that question may have been(probably not) correct at the time. The C++11 standard did not have the "Every value of pointer type is one of the following:" statement, and a pointer may have been only invalid if it referred to deleted space. – Noch Feb 10 '20 at 09:55

2 Answers2

8

[basic.compound] says:

Every value of pointer type is one of the following:

  • a pointer to an object or function (the pointer is said to point to the object or function), or
  • a pointer past the end of an object ([expr.add]), or
  • the null pointer value ([conv.ptr]) for that type, or
  • an invalid pointer value.

By the process of elimination we can deduce that p is an invalid pointer value.

[basic.stc] says:

Indirection through an invalid pointer value and passing an invalid pointer value to a deallocation function have undefined behavior. Any other use of an invalid pointer value has implementation-defined behavior.

As indirection operator is said to perform indirection by [expr.unary.op], I would say, that expression *p causes UB no matter if the result is used or not.

Revolver_Ocelot
  • 8,609
  • 3
  • 30
  • 48
  • Well, this is clearly the answer. Which also proves the linked question accepted the wrong answer. – Passer By Nov 10 '17 at 18:29
  • @PasserBy It is worth noting that implementation is allowed to create objects, and if it have relaxed pointer safety, access through _not-safely-derived_ pointers to valid objects might be legal. It allows for something like `volatile int* output_pins = reinterpret_cast(0x0800);` to be legal in particular implementation (like embedded programming). I assumed that `0xbadface` is intended to not be pointer to valid object. – Revolver_Ocelot Nov 10 '17 at 18:37
  • What if 0xbadface points to a hardware integer mapped into memory space? – CAF Nov 11 '17 at 00:27
  • _By the process of elimination we can deduce that p is an invalid pointer value._ 100% disagree. How have you managed to deduce this? Mapping from integers to pointers and vice versa is mostly implementation-defined and **nothing** forbids an implementation to give you a _pointer to_ some existing object as the result of `reinterpret_cast(0xbadface)`. – Language Lawyer Feb 02 '19 at 11:17
1

... some platforms trap on indirection for invalid pointers.

Most platforms trap on invalid address access. This does not contradict the issue in any way. The question of what happens in *p; boils down to whether an attempt to actually fetch at an invalid address takes place or not.

The question of fetching is very similar to the core issue 232 (indirection through a null pointer). As you have already pointed out, *p; is a discarded value expression, and as such no lvalue-to-rvalue conversion ("fetching") takes place:

Tom Plum:

...it is only the act of "fetching", of lvalue-to-rvalue conversion, that triggers the ill-formed or undefined behavior.

And subsequently:

Notes from the October 2003 meeting:

We agreed that the approach in the standard seems okay: p = 0; *p; is not inherently an error. An lvalue-to-rvalue conversion would give it undefined behavior.


As to whether or not reinterpret_cast<int*>(0xbadface) produces a valid pointer, indeed in implementations with strict pointer safety, it wouldn't be a safely-derived pointer, and as such is invalid and any use of it is UB.

But in case of relaxed pointer safety the resulting pointer is valid (otherwise it would be impossible to use pointers returned from binary libraries and components written in C or other languages).

rustyx
  • 80,671
  • 25
  • 200
  • 267
  • What if `p` surely has an invalid pointer value? `int *p = new int; delete p; *p;`. Is it UB? – geza Nov 10 '17 at 20:18
  • Yes that's UB, an end of the duration of storage is reached. The implementation may explicitly check and trap on dereferencing a deleted pointer. – rustyx Nov 10 '17 at 21:00
  • But, there is no lvalue-to-rvalue conversion here. Is it a different case? A null pointer can be dereferenced (without l-to-r conversion), but an invalid one cannot? – geza Nov 10 '17 at 21:14
  • The random address is assumed not to point to an object or one past another and it is obviously not a null pointer. It could then only be an invalid pointer. [basic.stc] isn't giving you the definition of a invalid pointer, just one possible way to acquire one. – Passer By Nov 11 '17 at 12:06
  • Thanks, answer updated. IMHO the standard is bugged in regard to how applies the term [*indirection*](http://eel.is/c++draft/expr.unary.op#def:indirection) in \[basic.stc\]. I think they meant to say "*indirection followed by an l-to-r conversion*". – rustyx Nov 11 '17 at 21:00