C++ Logo

SG12

Advanced search

Subject: Re: [ub] type punning through congruent base class?
From: Kazutoshi Satoda (k_satoda_at_[hidden])
Date: 2014-01-19 02:12:37


On 2014/01/19 11:54 +0900, Gabriel Dos Reis wrote:
> On 2014/01/19 3:23 +0900, Kazutoshi Satoda wrote:
> | There is an example which (I think) illustrates a real-world problem
> | caused by this assertion "the effective type of the object is just int
> | and not B." Please see this code and the result with gcc 4.8.2 (-O2).
> | http://melpon.org/wandbox/permlink/BJQkgZDmbsHEp71j
> |
> | struct A { int a; };
> | struct B { int a; double b; };
> |
> | int f(struct A* a, struct B* b)
> | {
> | b->a = 123;
> | a->a = 456;
> | return b->a;
> | }
(snip)
> From the C++ side, I see at least two issues with your program (but I
> think they are conceptually the same.)
> The return statement performs a *read* using a path "b->a" that isn't
> valid under 3.10/10 (that you cite below). I would expect the use of
> this path in a write access to be OK, but I wouldn't expect it to OK for
> a read before the object construction is finished since there is no
> dynamic type set yet.

I want you to elaborate the two points here.

Why the read isn't valid while a write to the same object through a
lvalue of the same type is valid?
In C++, the current aliasing rule is applied to "access" (which means
read or modify, according to DR #1531) and it doesn't treat read and
write differently. I think they are both valid or both invalid.
Probably we need a precise definition of "reuse" to treat some kind
of write access differently as such.
DR #1531:
  http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#1531

What rule do make the "path" of an access affect the validity of the
access?
I don't see such a rule in the current aliasing rule, then I'm proposing
such a rule. If you interpret "through" in 3.10 p10 means such a rule, I
think it is defective because one can bypass the restriction by first
obtaining a pointer (or reference) to the member without accessing the
stored value and accessing to the stored value later via the pointer
without a path through lvalue of B:
  int* pba = &b->a; // valid because no access to the stored value
  return *pba; // valid because access through lvalue of int, not through B

> | (going back to analysis in C)
(snip)
> | > Aha, but what about 6.5/6
> | >
> | > [...] For all other accesses to an object having no declared type,
> | > the effective type of the object is simply the type of the lvalue use
> | > for the access.
> | >
> | > ?
> |
> | A storing access determines the effective type for subsequent
> | non-modifying accesses. And such subsequent non-modifying accesses do
> | not drop into "all other accesses", I think.
>
> Hmm, that is interesting and an interpretation I didn't consider
> earlier; but why would not they fall under "all other accesses"?

Because otherwise the wording "and for subsequent accesses that do not
modify the stored value" becomes essentially void. And if effective type
for such read access was simply the type of the lvalue, no mismatch of
effective type and lvalue type would happen for allocated objects. Such
interpretation would spoil large parts of optimization based on the
aliasing rule.

> | Then, after "struct B x = *p", the effective type of the object at p
> | and &p->x are both still int.

-- 
k_satoda

SG12 list run by herb.sutter at gmail.com