Date: Fri, 17 Jan 2014 23:49:33 +0000
No, we would have exception for non-static data member access for unconstructed objects – this would be conceptually the same form of exception we grant in constructors for vcalls or typeids and even certain member access.
From: ub-bounces_at_[hidden] [mailto:ub-bounces_at_open-std.org] On Behalf Of Richard Smith
Sent: Friday, January 17, 2014 3:43 PM
To: WG21 UB study group
Subject: Re: [ub] type punning through congruent base class?
On 17 January 2014 15:19, Gabriel Dos Reis <gdr_at_[hidden]<mailto:gdr_at_[hidden]>> wrote:
| From what I've learned in this thread, the (rough) intended C++ model for
| PODs (assuming memory of the right size/alignment) would seem to be "the
| lifetime of a B starts when you write to the memory as a B, and ends when you
| free the memory or write to the memory as different type." [Disclaimer: I'm
| not sure if "read from the memory as a B" also starts lifetime."]
that would be close to what C does with effective types.
| I think we can do better,
Same here.
| but it seems like that's the (rough) intent of the
| status quo, leaving aside the question of whether the wording actually says
| that.
|
|
| *If* that is the (rough) intent, then in:
|
|
| void *p = malloc(sizeof(B)); // 1
|
| B* pb = (B*)p; // 2
|
| pb->i = 0; // 3
|
| short* ps = (short*)p; // 4
| *ps = 0; // 5
|
| free(p); // 6
|
|
|
|
| I assume that the reasoning would be that:
|
| * line 3 starts the lifetime of a B (we're writing to the bits of a B member,
| not just any int)
line 3 does not write as a B, so it wouldn't count -- just like in C.
Would that not give 'pb->i = 0' undefined behavior, because 'pb' does not, in fact, point to a B object?
The only clear governing rule I can find is 3.8/5, but that only applies if the storage ever actually contains a B object. Consider:
void *p = malloc(sizeof(B));
B *pb = (B*)p;
pb->i = 0;
// ...
new (p) B;
In this example, "pb->i = 0" has undefined behavior, because we performed member access on a pointer that points to storage where a B object will later be constructed. It seems strange that the definedness of 'pb->i = 0' would depend on how the storage is used later.
| * line 5 ends the lifetime of that B and begins the lifetime of a short
| * line 6 ends the lifetime of that short
Agreed.
| Again ignoring whether this is desirable, is that (roughly) the intent of the
| current wording?
Almost: we never started the lifetime of a B object.
| If yes, does the wording express it (a) accurately and (b) clearly?
My understanding is "no" for both.
Hear, hear =)
| Finally, regardless of the above answer, do we want to change anything about
| the legality or semantics of the above type-punning code, such as possibly
| having a "type-safe mode" where such code is somehow not allowed unless in
| an "extern "C-compat"" block or something?
This is a good question! Which definitely needs a paper :-)
My view is that if the storage isn't declared or accessed as a B, then it isn't a B .
It seems you don't consider member access as a B to qualify here. Is that your intent?
From: ub-bounces_at_[hidden] [mailto:ub-bounces_at_open-std.org] On Behalf Of Richard Smith
Sent: Friday, January 17, 2014 3:43 PM
To: WG21 UB study group
Subject: Re: [ub] type punning through congruent base class?
On 17 January 2014 15:19, Gabriel Dos Reis <gdr_at_[hidden]<mailto:gdr_at_[hidden]>> wrote:
| From what I've learned in this thread, the (rough) intended C++ model for
| PODs (assuming memory of the right size/alignment) would seem to be "the
| lifetime of a B starts when you write to the memory as a B, and ends when you
| free the memory or write to the memory as different type." [Disclaimer: I'm
| not sure if "read from the memory as a B" also starts lifetime."]
that would be close to what C does with effective types.
| I think we can do better,
Same here.
| but it seems like that's the (rough) intent of the
| status quo, leaving aside the question of whether the wording actually says
| that.
|
|
| *If* that is the (rough) intent, then in:
|
|
| void *p = malloc(sizeof(B)); // 1
|
| B* pb = (B*)p; // 2
|
| pb->i = 0; // 3
|
| short* ps = (short*)p; // 4
| *ps = 0; // 5
|
| free(p); // 6
|
|
|
|
| I assume that the reasoning would be that:
|
| * line 3 starts the lifetime of a B (we're writing to the bits of a B member,
| not just any int)
line 3 does not write as a B, so it wouldn't count -- just like in C.
Would that not give 'pb->i = 0' undefined behavior, because 'pb' does not, in fact, point to a B object?
The only clear governing rule I can find is 3.8/5, but that only applies if the storage ever actually contains a B object. Consider:
void *p = malloc(sizeof(B));
B *pb = (B*)p;
pb->i = 0;
// ...
new (p) B;
In this example, "pb->i = 0" has undefined behavior, because we performed member access on a pointer that points to storage where a B object will later be constructed. It seems strange that the definedness of 'pb->i = 0' would depend on how the storage is used later.
| * line 5 ends the lifetime of that B and begins the lifetime of a short
| * line 6 ends the lifetime of that short
Agreed.
| Again ignoring whether this is desirable, is that (roughly) the intent of the
| current wording?
Almost: we never started the lifetime of a B object.
| If yes, does the wording express it (a) accurately and (b) clearly?
My understanding is "no" for both.
Hear, hear =)
| Finally, regardless of the above answer, do we want to change anything about
| the legality or semantics of the above type-punning code, such as possibly
| having a "type-safe mode" where such code is somehow not allowed unless in
| an "extern "C-compat"" block or something?
This is a good question! Which definitely needs a paper :-)
My view is that if the storage isn't declared or accessed as a B, then it isn't a B .
It seems you don't consider member access as a B to qualify here. Is that your intent?
Received on 2014-01-18 00:49:51