cplusplus / CWG

Core Working Group
24 stars 7 forks source link

[basic.life] p7 A glvalue and a pointer referring/pointing to an ended lifetime object are defined different when accessing a non-static data member #582

Open xmh0511 opened 3 months ago

xmh0511 commented 3 months ago

Full name of submitter (unless configured in github; will be published with the issue): Jim X

[basic.life] p6 says:

The program has undefined behavior if :

  • [...]
  • the pointer is used to access a non-static data member or call a non-static member function of the object, or

However, [basic.life] p7 says:

Similarly, ... The program has undefined behavior if

  • the glvalue is used to access the object, or
  • the glvalue is used to call a non-static member function of the object, or
struct A{ int a; char b;};
int main(){
   A* ptr = new A{0,1};
   ptr->~A();
   ptr->a; // undefined 
   (*ptr).a; // ??
}

As said in [basic.life] p7, the behavior of a glvalue should be defined similarly to a pointer in this aspect, however, [basic.life] p7 does not cover when accessing a non-static data member. "the object" in the first bullet presumably means the object the glvalue refers to.

Suggested Resolution

  • the glvalue is used to access the object or subobjects thereof, or

or

  • the glvalue is used to access a non-static data member or call a non-static member function of the object, or
t3nsor commented 3 months ago

That's covered by [class.cdtor]/3

xmh0511 commented 3 months ago

That's covered by [class.cdtor]/3

[class.cdtor] is about the lifetime during construction and destruction. In my example, neither is.

t3nsor commented 3 months ago

That's covered by [class.cdtor]/3

[class.cdtor] is about the lifetime during construction and destruction. In my example, neither is.

You'll notice that nothing in [class.cdtor] actually says that all the rules in that section apply only to objects under construction and destruction. [class.cdtor]/3 applies to all objects that have non-static members. If the construction has not started or the destruction has completed, the behavior is undefined.

xmh0511 commented 3 months ago

That's covered by [class.cdtor]/3

[class.cdtor] is about the lifetime during construction and destruction. In my example, neither is.

You'll notice that nothing in [class.cdtor] actually says that all the rules in that section apply only to objects under construction and destruction. [class.cdtor]/3 applies to all objects that have non-static members. If the construction has not started or the destruction has completed, the behavior is undefined.

[class.cdtor] p3

To form a pointer to (or access the value of) a direct non-static member of an object obj, the construction of obj shall have started and its destruction shall not have completed, otherwise the computation of the pointer value (or accessing the member value) results in undefined behavior.

intends to cover the example I gave, however, [basic.life] p6 and p7 says:

For an object under construction or destruction, see [class.cdtor]. Otherwise, such a pointer refers to allocated storage ([basic.stc.dynamic.allocation]), and using the pointer as if the pointer were of type void* is well-defined. Indirection through such a pointer is permitted but the resulting lvalue may only be used in limited ways, as described below. The program has undefined behavior if

In my example, the object is not under construction or destruction, so, it should fall into Otherwise branch.

frederick-vs-ja commented 3 months ago

IIUC the example doesn't match the title because neither ptr->a nor (*ptr).a performs access (which is either reading or writing) itself. And... isn't the example just containing well-defined behavior, given A is a trivial type and the operations after the destruction only refer to a member subobject (see also [class.cdtor] p1)?

xmh0511 commented 3 months ago

IIUC the example doesn't match the title because neither ptr->a nor (*ptr).a performs access (which is either reading or writing) itself. And... isn't the example just containing well-defined behavior, given A is a trivial type and the operations after the destruction only refer to a member subobject (see also [class.cdtor] p1)?

struct A{ int a; char b;};
int main(){
   A* ptr = new A{0,1};
   ptr->~A();
   int a = ptr->a; // undefined 
   int a2 = (*ptr).a; // ??
}

The example should be this, which has access to the int subobject.

frederick-vs-ja commented 3 months ago
struct A{ int a; char b;};
int main(){
   A* ptr = new A{0,1};
   ptr->~A();
   int a = ptr->a; // undefined 
   int a2 = (*ptr).a; // ??
}

I think current wording already indicates that int a2 = (*ptr).a; is UB, because a is not within its lifetime and [basic.life] p7 already covers this. In this case, the glvalue is (*ptr).a, and we don't need to check whether access via (*ptr) is well-defined (IMO it's not, see below).

Also, as clarified in cplusplus/draft#4777, it should be clear that when a subobject is accessed. the containing object is also accessed.

Moreover, per the transformation in [expr.ref] p2, when a pointer is used to access a non-static data member or call a non-static member function of the object, the operation is transformed into the form using a glvalue. So IIUC [basic.life] p6.2 is redundant?

languagelawyer commented 3 months ago

Also, as clarified in https://github.com/cplusplus/draft/pull/4777, it should be clear that when a subobject is accessed. the containing object is also accessed.

>:-O

t3nsor commented 3 months ago

In my example, the object is not under construction or destruction, so, it should fall into Otherwise branch.

The wording here could definitely be improved, but I believe the intent is that if the object is under construction or destruction, then you get the additional permissions in [class.cdtor], but the restrictions in [class.cdtor] apply always.