2

In C++, given ST *p, *p produces an lvalue (expr.unary.op#1), and if the static type ST is primitive converting *p to an rvalue actually accesses and reads *p (conv.lval, basic.lval#11). But suppose we don't convert *p to an rvalue. We can evaluate &*p, or bind a reference to *p, etc. p must still point to an object or function of dynamic type DT. Must ST be somehow related to DT? Strictly speaking, rules &*p might even require ST = DT, but that's absurd because it's more restrictive than rules about actual accesses! Quoting expr.unary.op#1:

The unary * operator performs indirection. Its operand shall be a prvalue of type “pointer to T”, where T is an object or function type. The operator yields an lvalue of type T denoting the object or function to which the operand points.

Hence the title question: Must lvalues of type T point to objects of type T? "Clearly not", I think, but does the standard answer this anywhere?


I expect &*p must be less restrictive than accessing *p, and can be more restrictive than evaluating p — and those cases have clearer rules:

  1. accessing pointers has strict requirements ("strict aliasing"): actual accesses are only defined behavior if static type ST belongs to a certain list of types; this list includes more than dynamic type DT; specifically, ST can be DT, its signed/unsigned variants, and char, unsigned char, and std::byte (basic.lval#11).
  2. pointers themselves have no such requirement: if we just evaluate pointer p itself, static type ST and dynamic type DT need not be related, because a static_cast from void* to an arbitrary type has defined behavior if alignment requirements are satisfied (expr.static.cast#14).

Note: according to https://stackoverflow.com/a/21053756/53974, it seems (only?) expected that p points to an object or function.


EDIT: For concreteness, consider function f below.

#include <iostream>

short* f(int *ip) { // assume `p` actually points to DT = int
  void *vp = ip;
  // ^^ legal, per https://eel.is/c++draft/conv.ptr#2

  short* sp = static_cast<short*> (vp);
  // legal, per [expr.static.cast#14]
  return &*sp;
}

int main(int argc, char ** argv) {
    int i = 0;
    short* sp = f(&i);
    void* vp = sp;
    int* ip = static_cast<int*>(vp);
    *ip = 1;
    std::cout << "I: " << i << std::endl;

    return 0;
}

user3840170
  • 26,597
  • 4
  • 30
  • 62
Blaisorblade
  • 6,438
  • 1
  • 43
  • 76
  • 6
    *Must lvalues of type T point to objects of type T?* What? An object of type `T` doesn't point to a `T`, it is a `T` – NathanOliver May 14 '23 at 23:40
  • 2
    If `DT` is unrelated to `ST`, how did you make `p` point to `DT` in the first place? Most likely, you've invoked undefined behavior at that point. Can you show some code illustrating what you have in mind? – Igor Tandetnik May 14 '23 at 23:56
  • 1
    The current draft has removed a sentence that was in C++17, " If the type of the expression is “pointer to T ”, the type of the result is “ T ”". There must be a PR behind that change that might give some rationale. I have certainly seen it argued in the past that the behaviour is undefined by omission if the lvalue is evaluated at such time as there is not an object of type `T` at the designated location – M.M May 15 '23 at 00:33
  • @NathanOliver Typo, yes, so I fixed that to "identify" — I can use "determine the identity" if you prefer https://eel.is/c++draft/basic.lval#1.1. – Blaisorblade May 15 '23 at 00:36
  • @IgorTandetnik I added some code but no, a cast from `void*` doesn't need UB if you respect alignment requirements. – Blaisorblade May 15 '23 at 00:37
  • 2
    @M.M not removed, but changed to not to misuse «result». _I have certainly seen it argued in the past that the behaviour is undefined by omission if the lvalue is evaluated at such time as there is not an object of type `T` at the designated location_ I think you are confusing with UB by omission for `*` applied to a null pointer, because [expr.unary.op]/1 says «lvalue referring to the object or function to which the expression points», which does not apply in this case. This «implicit UB» interpretation still apply to the current draft wording. – Language Lawyer May 15 '23 at 04:24

1 Answers1

3

The quoted passage does not impose any requirement on the (dynamic) type of the object in question. If you initialize

int i;
auto &r=*reinterpret_cast<unsigned*>(&i);

the result of the cast refers to i despite having the type unsigned*, and similarly r is an lvalue of type unsigned that refers to the int object i (not undefined behavior from trying to refer to a nonexistent unsigned object at that address). Accessing i via that lvalue is blessed by [basic.lval]/11 (as mentioned), although the standard fails to indicate the actual effects of such access. (Presumably the bit pattern is used/updated, but there are subtleties when, say, the bytes of a pointer variable are mutated through a char lvalue.)

Davis Herring
  • 36,443
  • 4
  • 48
  • 76