Date: Thu, 16 Jan 2014 19:04:52 +0000
| -----Original Message-----
| From: ub-bounces_at_open-std.org [mailto:ub-bounces_at_open-std.org] On
| Behalf Of Matt Austern
| Sent: Thursday, January 16, 2014 10:30 AM
| To: WG21 UB study group
| Subject: Re: [ub] type punning through congruent base class?
|
| On Thu, Jan 16, 2014 at 10:04 AM, Gabriel Dos Reis <gdr_at_microsoft.com
| <mailto:gdr_at_microsoft.com> > wrote:
|
|
| > Casting a pointer from T* to U*, in and by itself, does not necessarily
| > establish or end a lifetime.
|
|
| Indeed. Richard's messages make me a little uneasy about just what does
| begin the lifetime of a POD object, though. I'm thinking of:
| struct B { int x; }; // 1
| void* p = malloc(sizeof(B)); // 2
| B* pb = static_cast<B*>(p); //3
| pb->x = 17; // 4
|
| I take it as obvious that the lifetime of an object of type B has begun
| somewhere in this code snippet. Now the question is: which specific line
| begins that lifetime? As you say, casting a pointer doesn't begin or end a
| lifetime; I think we can rule out line 3 as the one that begins the lifetime of
| that B object. Line 1 doesn't look too promising either.
Well, in fact I don't take it obvious that the lifetime of an object has even begun!
I don't even see that or object has been constructed or initialized.
| A literal reading of the standard ([basic.life]/1) suggests that it's line 2:
| "The lifetime of an object of type T begins when:
| — storage with the proper alignment and size for type T is obtained, and
| — if the object has non-trivial initialization, its initialization is complete."
|
| In line 2 we have obtained storage with the proper alignment and size for type
| B, and the second bullet item doesn't apply since B has no non-trivial
| initialization. None of the other lines are relevant to the beginning of the
| lifetime.
|
| But that's a little disturbing too. If we've got:
| struct X { int x; }; // 1'
| struct Y { int y; }; // 2'
| void* p = malloc(sizeof(X)); // 3'
| then does line 3' mean that the lifetime of some object has begun? Again a
| literal reading of that same standard text suggests yes. We have obtained
| storage with the proper alignment and size for type X, so the lifetime of an
| object of type X has begun. Exactly the same argument means that the
| lifetime of an object of type Y has begun, and similarly for every other type,
| mentioned or not, whose size is equal to sizeof(X).
|
| That seems like a crazy conclusion, but I don't see how to reject it without also
| rejecting the idea that the lifetime of an object of type B has been established
| somewhere in my first code snippet. What all of this suggests to me is that the
| concept of object lifetime needs a little more thought for types that don't have
| initialization.
|
| --Matt
I suspect part of the disturbance or incomfort is that we might be reading too much into the paragraph about obtaining storage or appropriate alignment and size.
As I said above, I don't see an object of type B being established -- this isn't argumentation for the sake of argumentation.
I think we should go back to the general principle - which works well for general objects - that an object lifetime starts right after its construct finishes and ends right before its destructor starts. I don't think we want to compromise that.
Of course, C-style data types (I'm focusing on what we used to call POD structs) have constructors and destructors - even when we call deem them trivial.
Declared objects should follow the same lifetime rule as any other object class type. That takes care of many of Richard's previous examples. It is even close to C's -- if we feel that we need to make a case for C-compatibility. What remains is the case of undeclared objects, that is an object with dynamic storage: (a) either a new-expression fires up the constructor (however trivial); or (b) a use memcpy or memmove has been applied to copy an object representation into that storage from a living object.
Note that, even when in C's model, it is not clear that memberwise assignment to fields automatically establishes lifetime for "the" complete object -- if there is one.
Note 1: at this point, I'm not considering the case where the storage of an object has been reused because I am assuming it isn't under dispute that it ends the lifetime of the object (if any) that previously occupied that storage. And that this is the same for all complete object types.
Note 2: I am not suggesting to adopt C's model - I'm merely mentioning it as data point.
-- Gaby
| From: ub-bounces_at_open-std.org [mailto:ub-bounces_at_open-std.org] On
| Behalf Of Matt Austern
| Sent: Thursday, January 16, 2014 10:30 AM
| To: WG21 UB study group
| Subject: Re: [ub] type punning through congruent base class?
|
| On Thu, Jan 16, 2014 at 10:04 AM, Gabriel Dos Reis <gdr_at_microsoft.com
| <mailto:gdr_at_microsoft.com> > wrote:
|
|
| > Casting a pointer from T* to U*, in and by itself, does not necessarily
| > establish or end a lifetime.
|
|
| Indeed. Richard's messages make me a little uneasy about just what does
| begin the lifetime of a POD object, though. I'm thinking of:
| struct B { int x; }; // 1
| void* p = malloc(sizeof(B)); // 2
| B* pb = static_cast<B*>(p); //3
| pb->x = 17; // 4
|
| I take it as obvious that the lifetime of an object of type B has begun
| somewhere in this code snippet. Now the question is: which specific line
| begins that lifetime? As you say, casting a pointer doesn't begin or end a
| lifetime; I think we can rule out line 3 as the one that begins the lifetime of
| that B object. Line 1 doesn't look too promising either.
Well, in fact I don't take it obvious that the lifetime of an object has even begun!
I don't even see that or object has been constructed or initialized.
| A literal reading of the standard ([basic.life]/1) suggests that it's line 2:
| "The lifetime of an object of type T begins when:
| — storage with the proper alignment and size for type T is obtained, and
| — if the object has non-trivial initialization, its initialization is complete."
|
| In line 2 we have obtained storage with the proper alignment and size for type
| B, and the second bullet item doesn't apply since B has no non-trivial
| initialization. None of the other lines are relevant to the beginning of the
| lifetime.
|
| But that's a little disturbing too. If we've got:
| struct X { int x; }; // 1'
| struct Y { int y; }; // 2'
| void* p = malloc(sizeof(X)); // 3'
| then does line 3' mean that the lifetime of some object has begun? Again a
| literal reading of that same standard text suggests yes. We have obtained
| storage with the proper alignment and size for type X, so the lifetime of an
| object of type X has begun. Exactly the same argument means that the
| lifetime of an object of type Y has begun, and similarly for every other type,
| mentioned or not, whose size is equal to sizeof(X).
|
| That seems like a crazy conclusion, but I don't see how to reject it without also
| rejecting the idea that the lifetime of an object of type B has been established
| somewhere in my first code snippet. What all of this suggests to me is that the
| concept of object lifetime needs a little more thought for types that don't have
| initialization.
|
| --Matt
I suspect part of the disturbance or incomfort is that we might be reading too much into the paragraph about obtaining storage or appropriate alignment and size.
As I said above, I don't see an object of type B being established -- this isn't argumentation for the sake of argumentation.
I think we should go back to the general principle - which works well for general objects - that an object lifetime starts right after its construct finishes and ends right before its destructor starts. I don't think we want to compromise that.
Of course, C-style data types (I'm focusing on what we used to call POD structs) have constructors and destructors - even when we call deem them trivial.
Declared objects should follow the same lifetime rule as any other object class type. That takes care of many of Richard's previous examples. It is even close to C's -- if we feel that we need to make a case for C-compatibility. What remains is the case of undeclared objects, that is an object with dynamic storage: (a) either a new-expression fires up the constructor (however trivial); or (b) a use memcpy or memmove has been applied to copy an object representation into that storage from a living object.
Note that, even when in C's model, it is not clear that memberwise assignment to fields automatically establishes lifetime for "the" complete object -- if there is one.
Note 1: at this point, I'm not considering the case where the storage of an object has been reused because I am assuming it isn't under dispute that it ends the lifetime of the object (if any) that previously occupied that storage. And that this is the same for all complete object types.
Note 2: I am not suggesting to adopt C's model - I'm merely mentioning it as data point.
-- Gaby
Received on 2014-01-16 20:04:58