Date: Wed, 15 Jan 2014 16:51:55 -0800
On Wed, Jan 15, 2014 at 4:32 PM, Herb Sutter <hsutter_at_[hidden]> wrote:
> I would expect that that we would have consensus that the lifetime of an
> object of type D *should* start at some point in this code (and more
> specifically that the code has defined behavior):
>
>
>
> B *p = (B*)malloc(sizeof(B));
>
> p->i = 0;
>
>
>
>
>
> (blink) D is not even mentioned, so why would we expect that? Can you
> explain?
>
Sorry, editing error, I meant 'B' here.
> If the standard said the lifetime of a D object started somewhere here, I
> would wonder if there’s a defect in the lifetime wording. That would seem
> to violate sensible notions of type safety.
>
Playing devil's advocate again: it would be entirely unobservable whether
an object of type 'B' or 'D' actually had its lifetime start here. Why
would you be surprised if you got a 'D' instead of a 'B'? Indeed, if later
I wrote:
D *q = (D*)p; // ha ha, it was a D object the whole time!
int n = q->i;
... why should that /not/ work? How is this any different from if I cast to
D* before I used the object as a B?
> *From:* ub-bounces_at_[hidden] [mailto:ub-bounces_at_[hidden]] *On
> Behalf Of *Richard Smith
> *Sent:* Wednesday, January 15, 2014 3:09 PM
>
> *To:* WG21 UB study group
> *Subject:* Re: [ub] type punning through congruent base class?
>
>
>
> On Wed, Jan 15, 2014 at 1:48 PM, Herb Sutter <hsutter_at_[hidden]>
> wrote:
>
> Richard, I'm not sure I understand your position... Given the following
> complete program ...
>
>
>
> struct B { int i; };
>
> struct D : B { };
>
>
>
> int main() {
>
> B b; // line X
>
> }
>
>
>
> ... are you actually saying that line X starts the lifetime of an object
> of type D? or just setting up a strawman? (Sorry, I really couldn't tell.)
>
>
>
> If yes, then given the following complete program ...
>
>
>
> struct C { long long i; };
>
>
>
> int main() {
>
> C c; // line Y
>
> }
>
>
>
> ... are you saying that line Y could start the lifetime of an object of
> type D (which is not mentioned in the code), double, shared_ptr<widget>, or
> any other type than C, as long as the size of that other type is the same
> or less than sizeof(C)?
>
>
>
> I think my position is more nuanced. There are a set of cases that I think
> people expect to have defined behavior, and the standard does not currently
> distinguish those cases from your cases above, as far as I can tell -- if
> using malloc to create an object "with trivial initialization" is
> acceptable, then it appears that *any* way of producing the
> appropriately-sized-and-aligned storage is acceptable.
>
>
>
>
>
> I would expect that that we would have consensus that the lifetime of an
> object of type D *should* start at some point in this code (and more
> specifically that the code has defined behavior):
>
>
>
> B *p = (B*)malloc(sizeof(B));
>
> p->i = 0;
>
>
>
> I would expect to also have consensus that the same is true here:
>
>
>
> alignof(B) char buffer[sizeof(B)];
>
> B *p = (B*)buffer;
>
> p->i = 0;
>
>
>
> (These expectations are based on the vast amount of existing code that
> relies upon these behaviors, not on the wording of the standard.)
>
>
>
> So, that's "what people probably expect". Next, "what the standard says".
> Consider 3.8/1:
>
>
>
> "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."
>
>
>
> This requires us to answer three questions: (1) is there an object of type
> B in the above snippets, (2) when is storage for it obtained, and (3) does
> it have non-trivial initialization? It seems that "yes, before the storage
> is used as an object of type B, no" is a self-consistent set of answers,
> and one that gives the program defined behavior. There are also sets of
> answers that give the program undefined behavior, and the standard doesn't
> give us direction in how we might pick a set of answers to these questions.
>
>
>
> There seem to be two obvious ways forward: either (a) if we can pick
> answers to these questions such that the program has defined behavior, then
> the program has defined behavior, or (b) if we can pick answers to these
> questions such that the program has undefined behavior, then the program
> has undefined behavior.
>
>
>
> If we want to align "what people probably expect" with "what the standard
> says", it seems we need to either change the above rule, or accept
> interpretation (a), under which the program above is valid, as is any other
> program where 'buffer' obtains storage of the appropriate size and
> alignment for an object of type D. (Option (a) also matches the behavior of
> current optimizing compilers, as far as I'm aware.)
>
>
>
>
>
> I've spent quite some time thinking about and discussing this problem and
> related issues (such as, under what circumstances does a pointer point to
> an object, when do two pointers alias, ...), and I personally think that
> the best approach here is to embrace option (a) above: if there exist a
> consistent set of choices of object lifetimes such that the program has
> defined behavior, then the program has the behavior implied by that set. (I
> have a sketch proof that such behavior is the same for *every* such
> consistent set, aside from deviations caused by unspecified values and
> other pre-existing sources of nondeterminism.) In essence, the implication
> of this is that objects' lifetimes would start just in time to avoid
> undefined behavior.
>
>
>
> Put another way, yes, I personally think this code should have defined
> behavior:
>
>
>
> C c; // #1
>
> static_assert(sizeof(C) >= sizeof(D) && alignof(C) >= alignof(D), "");
>
> D *p = (D*)&c;
>
> d->i = 0; // #2
>
>
>
> ... and the lifetime of a D object at address &c should start at some
> point between lines #1 and #2. (Naturally, the lifetime of the C object
> ended before this happened.) Moreover, this is something that plenty of
> existing C++ code relies on.
>
>
>
> *From:* ub-bounces_at_[hidden] <ub-bounces_at_[hidden]> on behalf of
> Richard Smith <richardsmith_at_[hidden]>
> *Sent:* Monday, January 6, 2014 3:44 PM
>
>
> *To:* WG21 UB study group
> *Subject:* Re: [ub] type punning through congruent base class?
>
>
>
> On Mon, Jan 6, 2014 at 10:22 AM, Jason Merrill <jason_at_[hidden]> wrote:
>
> On 01/06/2014 04:26 AM, Fabio Fracassi wrote:
> > if it is not (legal): could we make it legal or would we run afoul of
> > the aliasing rules?
>
> The access is not allowed by the aliasing rules in 3.10. But it seems
> that this would be:
>
>
> struct B {
> int i;
> };
>
> struct D {
>
> B bmem;
> void foo() { /* access bmem.i */ }
> };
>
> B b;
> reinterpret_cast<D&>(b).foo();
>
> because B is a non-static data member of D, and 9.2/19 guarantees that
> the address of D::bmem is the same as the address of the D object.
>
>
>
> How is that fundamentally different? 9.3.1/2 makes that UB too, if
> 'reinterpret_cast<D&>(b)' does not refer to an object of type 'b'. And
> within D::foo, the implicit this->bmem would have the same problem.
>
>
>
>
>
> If I might play devil's advocate for a moment...
>
>
>
> struct B { int i; };
>
> struct D : B {
>
> void foo();
>
> };
>
>
>
> B b;
>
>
>
> I claim this line starts the lifetime of an object of type D. Per
> [basic.life]p1, the lifetime of an object of type 'D' begins when storage
> with the proper alignment and size for type T is obtained (which "B b"
> happens to satisfy). The object does not have non-trivial initialization,
> so the second bullet does not apply.
>
>
>
> (This is the same argument that makes this valid:
>
>
>
> D *p = (D*)malloc(sizeof(D));
>
> p->foo();
>
>
>
> ... so any counterargument will need to explain why the two cases are
> fundamentally different.)
>
>
>
> Then:
>
>
>
> reinterpret_cast<D&>(b).foo();
>
>
>
> ... is valid, because the cast produces the same memory address, and that
> memory address contains an object of type 'D' (as claimed above).
>
>
> _______________________________________________
> ub mailing list
> ub_at_[hidden]
> http://www.open-std.org/mailman/listinfo/ub
>
>
> _______________________________________________
> ub mailing list
> ub_at_[hidden]
> http://www.open-std.org/mailman/listinfo/ub
>
>
> I would expect that that we would have consensus that the lifetime of an
> object of type D *should* start at some point in this code (and more
> specifically that the code has defined behavior):
>
>
>
> B *p = (B*)malloc(sizeof(B));
>
> p->i = 0;
>
>
>
>
>
> (blink) D is not even mentioned, so why would we expect that? Can you
> explain?
>
Sorry, editing error, I meant 'B' here.
> If the standard said the lifetime of a D object started somewhere here, I
> would wonder if there’s a defect in the lifetime wording. That would seem
> to violate sensible notions of type safety.
>
Playing devil's advocate again: it would be entirely unobservable whether
an object of type 'B' or 'D' actually had its lifetime start here. Why
would you be surprised if you got a 'D' instead of a 'B'? Indeed, if later
I wrote:
D *q = (D*)p; // ha ha, it was a D object the whole time!
int n = q->i;
... why should that /not/ work? How is this any different from if I cast to
D* before I used the object as a B?
> *From:* ub-bounces_at_[hidden] [mailto:ub-bounces_at_[hidden]] *On
> Behalf Of *Richard Smith
> *Sent:* Wednesday, January 15, 2014 3:09 PM
>
> *To:* WG21 UB study group
> *Subject:* Re: [ub] type punning through congruent base class?
>
>
>
> On Wed, Jan 15, 2014 at 1:48 PM, Herb Sutter <hsutter_at_[hidden]>
> wrote:
>
> Richard, I'm not sure I understand your position... Given the following
> complete program ...
>
>
>
> struct B { int i; };
>
> struct D : B { };
>
>
>
> int main() {
>
> B b; // line X
>
> }
>
>
>
> ... are you actually saying that line X starts the lifetime of an object
> of type D? or just setting up a strawman? (Sorry, I really couldn't tell.)
>
>
>
> If yes, then given the following complete program ...
>
>
>
> struct C { long long i; };
>
>
>
> int main() {
>
> C c; // line Y
>
> }
>
>
>
> ... are you saying that line Y could start the lifetime of an object of
> type D (which is not mentioned in the code), double, shared_ptr<widget>, or
> any other type than C, as long as the size of that other type is the same
> or less than sizeof(C)?
>
>
>
> I think my position is more nuanced. There are a set of cases that I think
> people expect to have defined behavior, and the standard does not currently
> distinguish those cases from your cases above, as far as I can tell -- if
> using malloc to create an object "with trivial initialization" is
> acceptable, then it appears that *any* way of producing the
> appropriately-sized-and-aligned storage is acceptable.
>
>
>
>
>
> I would expect that that we would have consensus that the lifetime of an
> object of type D *should* start at some point in this code (and more
> specifically that the code has defined behavior):
>
>
>
> B *p = (B*)malloc(sizeof(B));
>
> p->i = 0;
>
>
>
> I would expect to also have consensus that the same is true here:
>
>
>
> alignof(B) char buffer[sizeof(B)];
>
> B *p = (B*)buffer;
>
> p->i = 0;
>
>
>
> (These expectations are based on the vast amount of existing code that
> relies upon these behaviors, not on the wording of the standard.)
>
>
>
> So, that's "what people probably expect". Next, "what the standard says".
> Consider 3.8/1:
>
>
>
> "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."
>
>
>
> This requires us to answer three questions: (1) is there an object of type
> B in the above snippets, (2) when is storage for it obtained, and (3) does
> it have non-trivial initialization? It seems that "yes, before the storage
> is used as an object of type B, no" is a self-consistent set of answers,
> and one that gives the program defined behavior. There are also sets of
> answers that give the program undefined behavior, and the standard doesn't
> give us direction in how we might pick a set of answers to these questions.
>
>
>
> There seem to be two obvious ways forward: either (a) if we can pick
> answers to these questions such that the program has defined behavior, then
> the program has defined behavior, or (b) if we can pick answers to these
> questions such that the program has undefined behavior, then the program
> has undefined behavior.
>
>
>
> If we want to align "what people probably expect" with "what the standard
> says", it seems we need to either change the above rule, or accept
> interpretation (a), under which the program above is valid, as is any other
> program where 'buffer' obtains storage of the appropriate size and
> alignment for an object of type D. (Option (a) also matches the behavior of
> current optimizing compilers, as far as I'm aware.)
>
>
>
>
>
> I've spent quite some time thinking about and discussing this problem and
> related issues (such as, under what circumstances does a pointer point to
> an object, when do two pointers alias, ...), and I personally think that
> the best approach here is to embrace option (a) above: if there exist a
> consistent set of choices of object lifetimes such that the program has
> defined behavior, then the program has the behavior implied by that set. (I
> have a sketch proof that such behavior is the same for *every* such
> consistent set, aside from deviations caused by unspecified values and
> other pre-existing sources of nondeterminism.) In essence, the implication
> of this is that objects' lifetimes would start just in time to avoid
> undefined behavior.
>
>
>
> Put another way, yes, I personally think this code should have defined
> behavior:
>
>
>
> C c; // #1
>
> static_assert(sizeof(C) >= sizeof(D) && alignof(C) >= alignof(D), "");
>
> D *p = (D*)&c;
>
> d->i = 0; // #2
>
>
>
> ... and the lifetime of a D object at address &c should start at some
> point between lines #1 and #2. (Naturally, the lifetime of the C object
> ended before this happened.) Moreover, this is something that plenty of
> existing C++ code relies on.
>
>
>
> *From:* ub-bounces_at_[hidden] <ub-bounces_at_[hidden]> on behalf of
> Richard Smith <richardsmith_at_[hidden]>
> *Sent:* Monday, January 6, 2014 3:44 PM
>
>
> *To:* WG21 UB study group
> *Subject:* Re: [ub] type punning through congruent base class?
>
>
>
> On Mon, Jan 6, 2014 at 10:22 AM, Jason Merrill <jason_at_[hidden]> wrote:
>
> On 01/06/2014 04:26 AM, Fabio Fracassi wrote:
> > if it is not (legal): could we make it legal or would we run afoul of
> > the aliasing rules?
>
> The access is not allowed by the aliasing rules in 3.10. But it seems
> that this would be:
>
>
> struct B {
> int i;
> };
>
> struct D {
>
> B bmem;
> void foo() { /* access bmem.i */ }
> };
>
> B b;
> reinterpret_cast<D&>(b).foo();
>
> because B is a non-static data member of D, and 9.2/19 guarantees that
> the address of D::bmem is the same as the address of the D object.
>
>
>
> How is that fundamentally different? 9.3.1/2 makes that UB too, if
> 'reinterpret_cast<D&>(b)' does not refer to an object of type 'b'. And
> within D::foo, the implicit this->bmem would have the same problem.
>
>
>
>
>
> If I might play devil's advocate for a moment...
>
>
>
> struct B { int i; };
>
> struct D : B {
>
> void foo();
>
> };
>
>
>
> B b;
>
>
>
> I claim this line starts the lifetime of an object of type D. Per
> [basic.life]p1, the lifetime of an object of type 'D' begins when storage
> with the proper alignment and size for type T is obtained (which "B b"
> happens to satisfy). The object does not have non-trivial initialization,
> so the second bullet does not apply.
>
>
>
> (This is the same argument that makes this valid:
>
>
>
> D *p = (D*)malloc(sizeof(D));
>
> p->foo();
>
>
>
> ... so any counterargument will need to explain why the two cases are
> fundamentally different.)
>
>
>
> Then:
>
>
>
> reinterpret_cast<D&>(b).foo();
>
>
>
> ... is valid, because the cast produces the same memory address, and that
> memory address contains an object of type 'D' (as claimed above).
>
>
> _______________________________________________
> ub mailing list
> ub_at_[hidden]
> http://www.open-std.org/mailman/listinfo/ub
>
>
> _______________________________________________
> ub mailing list
> ub_at_[hidden]
> http://www.open-std.org/mailman/listinfo/ub
>
>
Received on 2014-01-16 01:51:57