Date: Fri, 17 Jan 2014 22:56:12 +0000
> Note that this post from Herb arrived after http://www.open-std.org/pipermail/ub/2014-January/000418.html but was sent before, so the thread got a little mixed up.
Yes, I've been trying to reply less on this thread until that sync'ed back up. :)
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."]
I think we can do better, 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 5 ends the lifetime of that B and begins the lifetime of a short
* line 6 ends the lifetime of that short
Again ignoring whether this is desirable, is that (roughly) the intent of the current wording?
If yes, does the wording express it (a) accurately and (b) clearly?
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?
Herb
________________________________
From: ub-bounces_at_open-std.org <ub-bounces_at_[hidden]> on behalf of Jeffrey Yasskin <jyasskin_at_[hidden]>
Sent: Friday, January 17, 2014 1:34 PM
To: WG21 UB study group
Subject: Re: [ub] type punning through congruent base class?
Note that this post from Herb arrived after http://www.open-std.org/pipermail/ub/2014-January/000418.html but was sent before, so the thread got a little mixed up.
On Thu, Jan 16, 2014 at 11:38 AM, Herb Sutter <hsutter_at_[hidden]<mailto:hsutter_at_[hidden]>> wrote:
Richard, it cannot mean that (or if it does, IMO we have an obvious bug) for at least two specific reasons I can think of (below), besides the general reasons that it would not be sensical and would violate type safety.
We do have an obvious bug in [basic.life]p1, "The lifetime of an object of type T begins when storage with the proper alignment and size for type T is obtained", if we interpret "obtained" as "obtained from the memory allocator". Even with strict uses of placement-new to change the type of memory, placement-new doesn't "obtain" any memory. If we interpret "obtained" as just "the programmer intends a region of storage to be available for a T", as I think Richard is suggesting, the bug is only that we need the wording to be clearer.
First, objects must have unique addresses. Consider, still assuming B is trivially constructible:
void *p = malloc(sizeof(B));
The lifetime of a B starts some time after-or-including the malloc() call in the above line and the access of 'pb->i' two lines down. [basic.life]p5 ("Before the lifetime of an object has started ... The program has undeļ¬ned behavior if ... the pointer is used to access a non-static data member")
The assignment to 'i' might start the lifetime of an 'int' subobject, but that's not enough to make the use of 'pb->i' defined if no 'B's lifetime has started.
B* pb = (B*)p;
pb->i = 0;
The lifetime of the B *ends* when its storage is re-used for the 'short' ([basic.life]p1 "The lifetime of an object of type T ends when ... the storage which the object occupies is reused"), as Daveed said. This happens some time after the access in the previous line, and the assignment two lines down.
short* ps = (short*)p;
*ps = 0;
This cannot possibly be construed as starting the lifetime of a B object and a short object, else they would have the same address, which is illegal. Am I missing something?
Both a B object and a short object have their lifetimes started in your code snippet, but the lifetimes don't overlap.
Confusingly, the start of these lifetimes is *not* called out in any particular line of code; it's implied by them. In particular, the casts don't have any lifetime effects (contra the straw man at http://www.open-std.org/pipermail/ub/2014-January/000406.html). The code would be just as defined (or undefined) written as:
void *p = malloc(sizeof(B));
B* pb = (B*)p;
short* ps = (short*)p;
pb->i = 0;
*ps = 0;
As Matt alluded to in http://www.open-std.org/pipermail/ub/2014-January/000456.html, it might be possible to say that all lifetime effects are called out in explicit expressions without breaking C compatibility, *if* we instead say that accessing the members of objects with trivial constructors can be done outside of the lifetime of such objects. I have no idea whether that would be better or worse than saying that lifetime effects can be implied.
Jeffrey
Yes, I've been trying to reply less on this thread until that sync'ed back up. :)
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."]
I think we can do better, 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 5 ends the lifetime of that B and begins the lifetime of a short
* line 6 ends the lifetime of that short
Again ignoring whether this is desirable, is that (roughly) the intent of the current wording?
If yes, does the wording express it (a) accurately and (b) clearly?
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?
Herb
________________________________
From: ub-bounces_at_open-std.org <ub-bounces_at_[hidden]> on behalf of Jeffrey Yasskin <jyasskin_at_[hidden]>
Sent: Friday, January 17, 2014 1:34 PM
To: WG21 UB study group
Subject: Re: [ub] type punning through congruent base class?
Note that this post from Herb arrived after http://www.open-std.org/pipermail/ub/2014-January/000418.html but was sent before, so the thread got a little mixed up.
On Thu, Jan 16, 2014 at 11:38 AM, Herb Sutter <hsutter_at_[hidden]<mailto:hsutter_at_[hidden]>> wrote:
Richard, it cannot mean that (or if it does, IMO we have an obvious bug) for at least two specific reasons I can think of (below), besides the general reasons that it would not be sensical and would violate type safety.
We do have an obvious bug in [basic.life]p1, "The lifetime of an object of type T begins when storage with the proper alignment and size for type T is obtained", if we interpret "obtained" as "obtained from the memory allocator". Even with strict uses of placement-new to change the type of memory, placement-new doesn't "obtain" any memory. If we interpret "obtained" as just "the programmer intends a region of storage to be available for a T", as I think Richard is suggesting, the bug is only that we need the wording to be clearer.
First, objects must have unique addresses. Consider, still assuming B is trivially constructible:
void *p = malloc(sizeof(B));
The lifetime of a B starts some time after-or-including the malloc() call in the above line and the access of 'pb->i' two lines down. [basic.life]p5 ("Before the lifetime of an object has started ... The program has undeļ¬ned behavior if ... the pointer is used to access a non-static data member")
The assignment to 'i' might start the lifetime of an 'int' subobject, but that's not enough to make the use of 'pb->i' defined if no 'B's lifetime has started.
B* pb = (B*)p;
pb->i = 0;
The lifetime of the B *ends* when its storage is re-used for the 'short' ([basic.life]p1 "The lifetime of an object of type T ends when ... the storage which the object occupies is reused"), as Daveed said. This happens some time after the access in the previous line, and the assignment two lines down.
short* ps = (short*)p;
*ps = 0;
This cannot possibly be construed as starting the lifetime of a B object and a short object, else they would have the same address, which is illegal. Am I missing something?
Both a B object and a short object have their lifetimes started in your code snippet, but the lifetimes don't overlap.
Confusingly, the start of these lifetimes is *not* called out in any particular line of code; it's implied by them. In particular, the casts don't have any lifetime effects (contra the straw man at http://www.open-std.org/pipermail/ub/2014-January/000406.html). The code would be just as defined (or undefined) written as:
void *p = malloc(sizeof(B));
B* pb = (B*)p;
short* ps = (short*)p;
pb->i = 0;
*ps = 0;
As Matt alluded to in http://www.open-std.org/pipermail/ub/2014-January/000456.html, it might be possible to say that all lifetime effects are called out in explicit expressions without breaking C compatibility, *if* we instead say that accessing the members of objects with trivial constructors can be done outside of the lifetime of such objects. I have no idea whether that would be better or worse than saying that lifetime effects can be implied.
Jeffrey
Received on 2014-01-17 23:56:17