3

Does forming a reference to an object constitute access?

Here's what GCC and Clang currently do:

void test(int const volatile* ptr) noexcept {
  *ptr;  // movl (%rdi), eax  // Reads *ptr
  [[maybe_unused]] int const volatile& ref = *ptr;  // Does not read *ptr
}

My question is specifically about the statement

  [[maybe_unused]] int const volatile& ref = *ptr;
  • According to the abstract machine, does this read the value of the object pointed to by ptr?
  • Would this statement, in isolation, be undefined behavior if ptr == nullptr?
  • Would it be an aliasing violation if ptr pointed to something other than an int?

Note that I ask specifically about forming the reference, and not about using it to read the value.

Edit 09/12/2019: Accepting the following answers:

  • Does int const volatile& ref = *ptr; read the value of the pointed-to object?
    • No.
  • Is this undefined when ptr == nullptr?
    • Yes, *ptr on a null pointer is undefined.
  • Is forming the reference an aliasing violation if ptr points to an object of different type?
    • No, just forming the reference does not violate strict aliasing.
    • Presumably reinterpret_cast-ing the reference to the correct type is allowed and valid.
curiousguy
  • 8,038
  • 2
  • 40
  • 58
Filipp
  • 1,843
  • 12
  • 26
  • For the second question, it is an UB, https://stackoverflow.com/questions/4364536/is-null-reference-possible – Renat Dec 06 '19 at 01:01
  • Forgive me asking, where does the language spec say that `*ptr;` by itself will cause a dereference and consequential memory-access? Wouldn't it be NOOP'd? Or is it explicitly allowed and not-NOOP'd because it could be dereferencing a hardware IO address which had some side-effect when read? – Dai Dec 06 '19 at 01:20
  • Can't cite so adding as a comment: a reference isn't even required to exist, it's just an alias. At most you are capturing the address of the object so no access should take place. – NathanOliver Dec 06 '19 at 01:34
  • A standalone `*ptr;` statement which points to a volatile is a different beast that will execute a read of the value and discard it. It's of use for hardware control and common in embedded systems. For instance where the read of the address is used to clock in other data. Only the access is important and not the value. Best to avoid volatile unless you are working on such things and know the compiler and hardware intimately. – doug Dec 06 '19 at 04:00
  • See http://eel.is/c++draft/defns.access – Language Lawyer Dec 06 '19 at 10:45
  • 2
    Forming the reference doesn't access the value, and is not a strict aliasing violation – M.M Dec 06 '19 at 11:34
  • @doug Volatile is an extremely useful tool that is too often denigrated. Answers that propose the use of volatile can even be deleted w/o any explanation or even a functional criticism. Sad! – curiousguy Dec 10 '19 at 21:00
  • @curiousguy I agree! Volatile is not only extremely useful, there are places in embedded coding where it is essential. The problem with volatile is that it's comingled with const but has quite different semantics. There are a ton of places where it's really hard to figure out what volatile should actually do. And compilers do different things in some of the more complex uses. The standards peeps have the problem of trying not to break existing code and there's a lot of that out there much like there is a lot of type punning code out there that "works." but should be done with bitcast<> – doug Dec 11 '19 at 00:08
  • @doug "_it's comingled with const but has quite different semantics_" I think that's an extremely good observation. That is true on many levels: 1) a read only access of an object doesn't care whether the object is const or not; volatile access OTOH is important 2) Constructing a const object is constructing that object normally (ctor call or init of the scalar value) and making it const; but constructing a truly volatile object should be diff if volatile is to be taken seriously. – curiousguy Dec 11 '19 at 01:36
  • "_There are a ton of places where it's really hard to figure out what volatile should actually do._" Many cases are clear: a volatile object must be represented according to the ABI. But then, should `register volatile` be supported? What should a cast to a volatile pointer do? "_The standards peeps have the problem of trying not to break existing code_" Frankly I don't see must respect for the existing code base in the C++ committee. They are very unreliable. What made them think narrow impl conversions are bad? – curiousguy Dec 11 '19 at 01:44
  • 1
    @curiousguy: The C and C++ committees expected compiler writers to recognize that saying "the Standard imposes no requirements" was an invitation for them to serve their customer's needs, not an invitation to ignore them. I don't blame the Committee for the crazy religion that has built itself around UB, but unfortunately suspect followers of the religion would block any attempt to include language within the Standard stating that things which are characterized as UB are *outside the Committee's jurisdiction* except in regard to *strictly* conforming programs. – supercat Feb 20 '20 at 22:01

1 Answers1

4

[basic.compound]/3 ... Every value of pointer type is one of the following:

(3.1) — a pointer to an object or function (the pointer is said to point to the object or function), or

(3.2) — a pointer past the end of an object (8.7), or

(3.3) — the null pointer value (7.11) for that type, or

(3.4) — an invalid pointer value.


[expr.unary.op]/1 The unary * operator performs indirection: the expression to which it is applied shall be a pointer to an object type, or a pointer to a function type and the result is an lvalue referring to the object or function to which the expression points.

Thus, the meaning of the expression *ptr is only defined for pointer ptr that points to an object or function - that is, a pointer whose value falls under [basic.compound]/(3.1). In all other cases, this expression exhibits undefined behavior.

Igor Tandetnik
  • 50,461
  • 4
  • 56
  • 85
  • Thank you for finding the part of the standard about which my question asks. Does the UB occur at `*ptr`, or only when the resulting lvalue is accessed? – Filipp Dec 06 '19 at 10:35
  • My reading of the standard is that `*ptr` in itself exhibits undefined behavior, regardless of what is done with the result. – Igor Tandetnik Dec 06 '19 at 15:08
  • "_the meaning of the expression *ptr is only defined for pointer ptr that points to an object or function_" You don't need for an object to exist to refer to it. – curiousguy Dec 10 '19 at 21:10