Topic: Defect Report #1116: Comment about changes


Author: Joshua Maurice <joshuamaurice@gmail.com>
Date: Fri, 14 Jan 2011 08:56:46 CST
Raw View
I had a discussion on comp.lang.c about this, found here:

http://groups.google.com/group/comp.lang.c/browse_thread/thread/eb13852f3d400a77#
and I've been thinking about the replies there.

It didn't resolve all of it - in fact I got at least 3 contradictory
replies. However, there was interesting insight given. I was under the
assumption that for foo:
   void foo(int* x, short* y)
   {
     *x = 1;
     *y = 2;
   }
Either the strict aliasing rules of the standard allow reordering the
writes, which can break some programs such as the union DR, or it can
never do aliasing analysis with the access rules of 3.10 / 15. A
different interpretation was given. That interpretation states that a
compiler may not reorder the writes in the foo above. However, for the
following function bar:
   int foo(int* x, short* y)
   {
     *x = 1;
     *y = 2;
     return *x;
   }
A compiler is free to reorder accesses to *x and *y. The reasoning is
that there can be a conforming program calling foo such that x and y
alias, but there can be no conforming program calling bar such that x
and y alias. If x and y alias in foo, then the write to *y in foo ends
the lifetime of the *x int object - no undefined behavior. However, if
x and y alias in bar, then *x starts the lifetime of an int object (if
there wasn't already one), *y ends the lifetime of the int object and
creates a short object, and finally there is a read of a short object
through an int lvalue - undefined behavior. Thus the program calling
bar with aliasing x and y has undefined behavior, and thus the
compiler is free to do whatever it wants with that undefined behavior
- namely assuming that x and y do not alias.

In short, this new interpretation is that the intent of the strict
aliasing rules was not the simple naive "A compiler may assume that an
int* and a short* do not alias", but instead requires a slightly more
complex analysis: before any "reordering", if undefined behavior were
to result from two pointers (or references, etc.) aliasing from the
access rules of 3.10 / 15, then the compiler is free to assume that
they do not alias.

Furthermore, if the user did want to tell the compiler that x and y do
not alias in bar, the user could use the restrict keyword. I'm not
sure that that's its intended use case, but it seems to apply under
this interpretation.

Frankly, I like this interpretation. I might like to know if it's the
original interpretation, but that's kind of a moot point. gcc seems to
do its own thing, and AFAIK no compiler uses this interpretation.
(Please prove me wrong. I'm not well versed on this topic.)

Either way, I do want to emphasis that the suggested resolution is an
incredibly bad one. It doesn't answer any of the important questions,
such as:
   #include <new>
   int main()
   {
     void* p = new char[sizeof(int) + sizeof(float)];
     new(p) int;
     new(p) short;
     new(p) float;
   }
Or:
   #include <cstdlib>
   #include <new>
   int main()
   {
     void* p = std::malloc(sizeof(int) + sizeof(float));
     new(p) int;
     new(p) short;
     new(p) float;
   }
The suggested wording doesn't make clear if you must ensure that the
new object type is "compatible" with all previous object types which
existed in that piece of memory, just the first, or just the
immediately preceding. The suggested wording doesn't even give an
example as to how this works with malloc, or other system specific
memory allocation functions which return "raw" memory, such as mmap.

IMHO, both should be allowed. Moreover, IMHO, malloc, new, etc.,
should not be treated specially in terms of the type system. This
whole idea of an "allocated type" is IMHO contrary to standard
practice where malloc and new are user-land memory allocators written
on top of mmap and the like. You also need both examples in this post
to work to have existing user-land memory allocators be correct.

I've given all of the questions else-thread which need answering for a
good and sufficient fix to the standard. I'm not sure I'm sufficiently
versed to give good answers to those questions, though I can try if
desired. Is any work being done on this? I would hate to see C++
diverge from C so drastically, and IMHO in quite the wrong direction.


--
[ comp.std.c++ is moderated.  To submit articles, try posting with your ]
[ newsreader.  If that fails, use mailto:std-cpp-submit@vandevoorde.com ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: Joshua Maurice <joshuamaurice@gmail.com>
Date: Sun, 6 Feb 2011 19:09:10 CST
Raw View
Through new discussions on comp.std.c, I am further convinced that
there is a real problem here, both in C and in C++. Consider the
following context for the following questions:

 #include <stddef.h>
 #include <stdlib.h>
 typedef struct T1 { int x; int y; } T1;
 typedef struct T2 { int x; int y; } T2;
 int main(void)
 {
   void* p = malloc(sizeof(T1));
   /* ... */
 }

Is there /any/ difference between
 ((T1*)p)->y = 2;
and
 * (int*) (((char*)p) + offsetof(T1, y)) = 2;
 for POD types (and member y of type int)? I am tempted to argue that
they are entirely equivalent. However, with that claim, we have a
serious problem. Consider:

 //program 1
 int main()
 { T1* p = (T1*) malloc(sizeof(T1));
   p->x = 1;
   p->y = 2;
   return p->y;
 }

The preceding is exactly equivalent to the following.

 //program 2
 int main()
 { T1* p = (T1*) malloc(sizeof(T1));
   * (int*) (((char*)p) + offsetof(T1, x)) = 1;
   * (int*) (((char*)p) + offsetof(T1, y)) = 2;
   return p->y;
 }

On win32 on x86 with basically any of the available compilers, the
preceding is entirely equivalent to the following. Moreover, on all
sane implementations, I can replace the offsetof macro with a
hardcoded integer literal and have entirely equivalent semantics and
behavior.

 //program 3
 int main()
 { T1* p = (T1*) malloc(sizeof(T1));
   * (int*) (((char*)p) + 0) = 1;
   * (int*) (((char*)p) + 4) = 2;
   return p->y;
 }

Now, I understand that offsetof(T1, y) may not equal offsetof(T2, y)
on a conforming implementation. However, it might on a conforming
implementation. (Actually, it will be equal on any sane
implementation, but I don't require that for this discussion.) On that
particular implementation, program 2 is entirely equivalent to the
following:

 //program 4
 int main()
 { T1* p = (T1*) malloc(sizeof(T1));
   * (int*) (((char*)p) + offsetof(T2, x)) = 1;
   * (int*) (((char*)p) + offsetof(T2, y)) = 2;
   return p->y;
 }

Which, as we established at the beginning, under my claim, the
preceding is entirely equivalent to the following:

 //program 5
 int main()
 { T1* p = (T1*) malloc(sizeof(T1));
   ((T2*)p)->x = 1;
   ((T2*)p)->y = 2;
   return p->y;
 }

Which is entirely equivalent to:

 //program 6
 int main()
 { T2* p = (T2*) malloc(sizeof(T1));
   ((T2*)p)->x = 1;
   ((T2*)p)->y = 2;
   return ((T1*)p)->y;
 }

So, there's my problem. I used one perhaps-dubious claim to convert an
idiomatic correct program to a program that has UB (for a particular
platform where T1 and T2 have the same memory layout). That one
dubious claim is:
   p->y = 2;
is entirely equivalent to
   * (int*) (((char*)p) + offsetof(T1, y)) = 2;
for POD types (and member y of type int).

Something has to go. Either we conclude:
1- "p->y = 2;" has different semantics than " * (int*) (((char*)p) +
offsetof(T1, y)) = 2;", or
2- if T1 and T2 have the same memory layout, then you can access any
T1 object through a T2 lvalue.

At least, I think that those are our options. Is there a third
option?

What to do?


--
[ comp.std.c++ is moderated.  To submit articles, try posting with your ]
[ newsreader.  If that fails, use mailto:std-cpp-submit@vandevoorde.com ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: Joshua Maurice <joshuamaurice@gmail.com>
Date: Wed, 9 Feb 2011 01:58:16 CST
Raw View
On Jan 14, 6:56 am, Joshua Maurice <joshuamaur...@gmail.com> wrote:
> I had a discussion on comp.lang.c about this, found here:
>
> http://groups.google.com/group/comp.lang.c/browse_thread/thread/eb138...
> and I've been thinking about the replies there.
>
> It didn't resolve all of it - in fact I got at least 3 contradictory
> replies. However, there was interesting insight given. I was under the
> assumption that for foo:
>    void foo(int* x, short* y)
>    {
>      *x = 1;
>      *y = 2;
>    }
> Either the strict aliasing rules of the standard allow reordering the
> writes, which can break some programs such as the union DR, or it can
> never do aliasing analysis with the access rules of 3.10 / 15. A
> different interpretation was given. That interpretation states that a
> compiler may not reorder the writes in the foo above. However, for the
> following function bar:
>    int foo(int* x, short* y)
>    {
>      *x = 1;
>      *y = 2;
>      return *x;
>    }
> A compiler is free to reorder accesses to *x and *y. The reasoning is
> that there can be a conforming program calling foo such that x and y
> alias, but there can be no conforming program calling bar such that x
> and y alias. If x and y alias in foo, then the write to *y in foo ends
> the lifetime of the *x int object - no undefined behavior. However, if
> x and y alias in bar, then *x starts the lifetime of an int object (if
> there wasn't already one), *y ends the lifetime of the int object and
> creates a short object, and finally there is a read of a short object
> through an int lvalue - undefined behavior. Thus the program calling
> bar with aliasing x and y has undefined behavior, and thus the
> compiler is free to do whatever it wants with that undefined behavior
> - namely assuming that x and y do not alias.
>
> In short, this new interpretation is that the intent of the strict
> aliasing rules was not the simple naive "A compiler may assume that an
> int* and a short* do not alias", but instead requires a slightly more
> complex analysis: before any "reordering", if undefined behavior were
> to result from two pointers (or references, etc.) aliasing from the
> access rules of 3.10 / 15, then the compiler is free to assume that
> they do not alias.
>
> Furthermore, if the user did want to tell the compiler that x and y do
> not alias in bar, the user could use the restrict keyword. I'm not
> sure that that's its intended use case, but it seems to apply under
> this interpretation.
>
> Frankly, I like this interpretation. I might like to know if it's the
> original interpretation, but that's kind of a moot point. gcc seems to
> do its own thing, and AFAIK no compiler uses this interpretation.
> (Please prove me wrong. I'm not well versed on this topic.)

Update:
 http://www.open-std.org/jtc1/sc22/wg14/www/docs/dr_236.htm
Apparently the C standard committee wants a compiler to be able to
optimize assuming that sufficiently differently typed pointers do not
alias /unless/ there is something in scope which makes alias - or
something. See the link for a better description.

Thus, we really need some clarification on the effective type rules of
C in light of this, as this greatly impacts how and if one can write a
conforming general purpose pooling memory allocator on top of malloc.
If you can't write such a memory allocator, then I think that's a
defect in the C standard.

I think that C++ ought to adopt whatever C adopts to maintain
backwards compatibility, especially when dealing with POD types only.
An idiomatic C general purpose pooling memory allocator on top of
malloc ought to be equally valid C++ code when used for POD types
only. Anything else is a defect in the C++ standard. /Moreover/, I
want any such C memory allocator to be usable for all C++ types, not
just POD types, and it ought to be a defect in the C++ standard if
this isn't the case.


--
[ comp.std.c++ is moderated.  To submit articles, try posting with your ]
[ newsreader.  If that fails, use mailto:std-cpp-submit@vandevoorde.com ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: "Johannes Schaub (litb)" <schaub-johannes@web.de>
Date: Fri, 1 Oct 2010 12:51:23 CST
Raw View
Daniel Kr=FCgler wrote:

> [4th posting attempt within the last 8 days...]
>
> On Sep 20, 5:19 am, "Johannes Schaub (litb)" <schaub-johan...@web.de>
> wrote:
>> I believe we are not talking about the same thing, so let me restate it.
>> My initial example can be equivalently taken from the example of 9.5/5 i=
n
>> n3126 which just contains the same code with different names and types.
>> Do you say that that example now is undefined behavior with #1116 applie=
d
>> and that such is intended?
>
> I don't believe that the current wording makes the
> example in the WP undefined behaviour. It should
> be explicitly allowed by the lvalue-via-union-rule in
> the bulleted list of 3.10.

Please see below. I would like to know what that union rule actually is. I
heard it a couple of times on usenet, but I can't make sense of that rule
when applied to this case.

> It is different from your
> example, though, which I repeat here for complete-
> ness and to be sure that we are speaking of the
> same thing:
>
> union A { int a; double b; } u;
> u.b = 0.0;
> u.a = 0;
> // Up to this point everything is fine
> double d = u.b; // This is your addition - at least implied
> // by the text you have added to the example.
>
> Since you discuss this example below, I proceed there.
>

That wasn't my addition. I was saying (or intended to say) in my text that
the proposed wording makes the belowing undefined:

union A { int a; double b; } u;
u.b = 0.0;
u.a = 0;

Of which you say it is fine. I wasn't adding the double read, but I was
saying that the proposed wording adds the double read when it says ".. such
that accessing the stored value of the B object through a glvalue of type A
would have undefined behavior (3.10 [basic.lval]), the behavior is
undefined.".

So, applying this rule to the above fine code, we get "... such that
accessing the stored value of the int object through a glvalue of type
double would have undefined behavior, the behavior is undefined.". Thus, th=
e
above fine code has undefined behavior under the proposed wording.

Just as the example in the WP.

>> I thought that's the whole point of how an union: Writing into one membe=
r
>> makes that member active by starting its lifetime and stopping the
>> lifetime of others by reusing their memory. It's all the reason the
>> following is undefined behavior:
>>
>> union A { int x; double y; };
>> A a;
>> a.x = 0;
>>  // member object now has type 'int'
>>
>> double d = a.y;
>>  // there this is undefined according to 3.10/15.
>>  // It tries to read an int by an lvalue of type double
>>
>> a.y = 0.0;
>>  // but now the member object has type 'double'. It's
>>  // not an int anymore!
>>
>> Do you really say that if we take the double read out ("double d =
>> a.y;"), the behavior is *still* undefined? What the whole point of an
>> union then? I once again think we are talking about different things.
>
> The behaviour is indeed intended to be undefined for
> this particular example after the P/R of 1116 has been
> applied.

If we take the double read out we get

union A { int x; double y; };

A a;
a.x = 0;
a.y = 0.0;

Which you say is fine under the proposed wording (and I believe the propose=
d
wording makes accidentally UB). I believe you meant the above to apply to
the version *with* the double read. Indeed that case is undefined, and has
always been undefined from what I know.

> But it is *not* so, because a.y is an alias
> violation under 3.10 (this would be an acceptable
> alias, because it's via a union type which has a
> double as one of its non-static data members).

I don't find such a "via-an-union" rule in 3.10. The only thing it says at
3.10/15 about unions is that one might access the stored value of an object
of type M through an union lvalue that has M as a member type. But in
"double d = a.y;" we access the int object by a double lvalue, not by an
union lvalue. We merely use an union type object in the member expression t=
o
build up the double lvalue.

The only sense I can make up from that union rule is to see it as allowing
the following:

A a;
...
A b = a; /* read an int or double object by an lvalue of type "A". */

Would you or someone else knowing the deep mystery about that please tell m=
e
how to interpret that union rule according to its actual intention and how
such an interpretation can actually be concluded from the wording. I would
really appreciate your time and effort.

> Instead, it's undefined behavior because the lifetime
> of a.y has *not* started, and the lvalue-to-rvalue
> conversion on a.y is not one of the acceptable uses
> of an lvalue referring to an out-of-lifetime object (see
> 3.8/6).
>

This is convincing. Actually, I can see how in my interpretation of 3.10/15
both your point with 3.8/6 and my point applies: The double object is out-
of-lifetime, while the int object is alive. So a double lvalue cannot be
used to read it by 3.10/15 for two reasons:

- There is no type defined for an out-of-lifetime object except for the
during-construction and during-destructor-execution cases. So no bullet
except the "unsigned char"/"char" cases of 3.10/15 apply; but there is an
int object alive at the place the lvalue refers to, still, no rule of
3.10/15 applies.

Of course this only applies for my own interpretation of 3.10/15. I would
really like to hear your explanation of how it makes unions in some way
special because 3.10/15 is purely semantical. It does not seem to take into
account any member-access expression weirdness like "a.y", I think.

In any case, since you say that by 3.8/6 reading "a.y" is undefined
behavior, why do you say that the example in the WP has defined behavior? I
mean, the proposed wording says if reading by "a.y" *would* have undefined
behavior. It does not actually require the program to read it for the
undefined behavior to take effect. Just like the WP does.


--
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use
mailto:std-c++@netlab.cs.rpi.edu<std-c%2B%2B@netlab.cs.rpi.edu>
]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: Joshua Maurice <joshuamaurice@gmail.com>
Date: Wed, 24 Nov 2010 13:41:38 CST
Raw View
[I'm unable to reply directly to the OP post via google groups for
some reason.]

I have my own questions as well, if there would be people kind enough
to answer them. I'm looking at the current wording at:
http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#1116

First, small question. I am curious how this is supposed to play out
with malloc. Presumably "malloc(x)" is interchangable with "new
char[x]" under this scheme (modulo char* vs void* expression type). Is
there a way to create an object which has an associated "allocation
type" besides "a new expression" and "a variable definition"? I
haven't fully thought this through yet, but it's nagging me that we're
treating new as special, but not a user defined allocation function,
all while a new expression is often implemented largely in userland C+
+ code on top of malloc.

Second, I agree fully with the changed wording to 3.8 Object
Lifetime / 1. That is a much needed fix. One quick question, are we
sure that all types are exactly one of "has non-trivial
initialization" or "trivially copyable"? Ex:
 class T
 {
   T() : x(0) {}
   int x;
 };
Type "T" has non-trivial initialization, correct? Type "T" is
trivially copyable, correct? This seems to defeat the purpose of the
proposed wording. What we need is wording that says the lifetime of an
object with a non-trivial constructor begins once its constructor
finishes (successfully), and the lifetime of an object with a trivial
constructor begins once a write is made to the storage through an
lvalue of that type. At least, something like that put into
standardeze.

Note that my phrasing is slightly different. Allow me to clarify it
with some examples:

 #include <new>
 int main()
 {
   char* c = new char[sizeof(int)];

   int* a = new(c) int;
   /*So, does an int object exist at this point in the storage "*c"?
By the proposed wording, and by my proposed wording, an int object
does not yet exist in the storage "*c".

   This seems bad that an int object does not actually exist yet,
even though we used placement new. Alternatively, we could say that an
int object does exist, but you can't read it yet because it has
uninitialized data, thus requiring that the first operation to the int
is a write, which is basically the same end AFAIK. */

   *a = 1;
   /* Ok. Now we have an int object. */
 }

Example:

 struct T { int x; int y; };
 int main()
 {
   char* c = new char[sizeof(T)];

   T* t = reinterpret_cast<T*>(c);
   /*Ok. No T object yet. */

   t->x = 1;
   /*So, do we have a T object or not? By the proposed wording, I
think no. We haven't copied the object representation of a /full/ T
object into the storage, so no T object exists. Thus a read "cout << t-
>x" would be undefined behavior according to the strict aliasing rules
of "3.10 Lvalues and rvalues / 15". I do not think that this is a good
way to define the standard. I think that "cout << t->x" should be
defined, and I that that "cout << t->y" should be undefined behavior
because it reads uninitialized data, a normal use of that normal rule.
*/

   t->y = 1;
   /*Ok, we better have a T object at this point. */
 }

Third. Let's look at the example on the issue page.

 int f() {
   union U { double d; } u1, u2;
   (int&)u1.d = 1;  //line 1
   u2 = u1;  //line 2
   return (int&)u2.d;
 }

>From my understanding of the C++03 standard, this is undefined
behavior. At line 1, the write to the storage through the int lvalue
reuses the storage, which ends the lifetime of all objects in that
storage (if any), and starts the lifetime of a new int object. At line
2, it accesses the storage of u1 which contains an int object through
a double lvalue. (An assignment of unions just assigns the first
member right? If I'm wrong, no matter. int is not involved, so it
doesn't change the rest of my reasoning.) This has undefined behavior
because it doesn't satisfy any of the bullets in "3.10 Lvalues and
rvalues / 15".

However, I am still concerned that we have problems. Let's consider:

 #include <iostream>
 using namespace std;
 int main()
 { char* c = new char[sizeof(int) + sizeof(double)];
   int* x = reinterpret_cast<int*>(c)
   double* d = reinterpret_cast<double*>(c)
   *x = 1;
   cout << *x << endl;
   *d = 1;
   cout << *d << endl;
 }

As far as I can tell, this is still legal under the sane
interpretation of C++03 (which includes my suggested revision of 3.8
Object Lifetime / 1), and this is still legal under the proposed
wording of issue 1116. Specifically, it would be illegal for a
compiler to conclude that *x and *d do not alias, and change the above
program to:

 #include <iostream>
 using namespace std;
 int main()
 { char* c = new char[sizeof(int) + sizeof(double)];
   int* x = reinterpret_cast<int*>(c)
   double* d = reinterpret_cast<double*>(c)
   *x = 1;
   *d = 1;
   cout << *x << endl;
   cout << *d << endl;
 }

Either the above is not a valid optimization, or we can't have generic
user defined memory allocators in pure conforming C++.

Thus, I really don't see the point of the proposed wording. It solves
nothing - use cases which must work are still at odds with the strict
aliasing rules, and it introduces a needless restriction related to
the memory allocation type.

As I was mentioning in a post in another thread on comp.lang.c+
+.moderated, the problem appears to be tightly related to the union
DR, specifically the following very similar program:

 #include <iostream>
 using namespace std;
 void foo(int* x, double* d)
 { *x = 1;
   cout << *x << endl;
   *d = 1;
   cout << *d << endl;
 }

 //separate translation unit
 void foo(int* x, double* d);
 int main()
 { union { int x; double d; };
   foo(&x, &d);
 }

Contrast with the equivalent when "reusing storage":

 #include <iostream>
 using namespace std;
 void foo(int* x, double* d)
 { *x = 1;
   cout << *x << endl;
   *d = 1;
   cout << *d << endl;
 }

 //separate translation unit
 void foo(int* x, double* d);
 int main()
 { char* c = new char[sizeof(int) + sizeof(double)];
   int* x = reinterpret_cast<int*>(c);
   double* d = reinterpret_cast<double*>(c);
   foo(x, d);
 }

Basically, they both need the same fix. I can't think of the
standardeze involved, but I think that the reasonable solution is to
require that aliasing pointers of sufficiently different types cannot
both "escape" the scope. This works for the union DR in addition to
the "reusing storage" problem. Also, from my limited understanding,
this is current practice as well - see: type punning through a union.


--
[ comp.std.c++ is moderated.  To submit articles, try posting with your ]
[ newsreader.  If that fails, use mailto:std-cpp-submit@vandevoorde.com ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: Joshua Maurice <joshuamaurice@gmail.com>
Date: Sat, 27 Nov 2010 00:50:28 CST
Raw View
On Nov 24, 11:41 am, Joshua Maurice <joshuamaur...@gmail.com> wrote:
> As I was mentioning in a post in another thread on comp.lang.c+
> +.moderated, the problem appears to be tightly related to the union
> DR, specifically the following very similar program:
>
>  #include <iostream>
>  using namespace std;
>  void foo(int* x, double* d)
>  { *x = 1;
>    cout << *x << endl;
>    *d = 1;
>    cout << *d << endl;
>  }
>
>  //separate translation unit
>  void foo(int* x, double* d);
>  int main()
>  { union { int x; double d; };
>    foo(&x, &d);
>  }
>
> Contrast with the equivalent when "reusing storage":
>
>  #include <iostream>
>  using namespace std;
>  void foo(int* x, double* d)
>  { *x = 1;
>    cout << *x << endl;
>    *d = 1;
>    cout << *d << endl;
>  }
>
>  //separate translation unit
>  void foo(int* x, double* d);
>  int main()
>  { char* c = new char[sizeof(int) + sizeof(double)];
>    int* x = reinterpret_cast<int*>(c);
>    double* d = reinterpret_cast<double*>(c);
>    foo(x, d);
>  }
>
> Basically, they both need the same fix. I can't think of the
> standardeze involved, but I think that the reasonable solution is to
> require that aliasing pointers of sufficiently different types cannot
> both "escape" the scope. This works for the union DR in addition to
> the "reusing storage" problem. Also, from my limited understanding,
> this is current practice as well - see: type punning through a union.

I was thinking about this more, and I still don't have a satisfactory
phrasing. I think no matter what, we'll need equivalents for:

rule 1: Reading uninitalized data is UB, except through (?) char or
unsigned char lvalues.

rule 2: Reading an object through a sufficiently differently typed
lvalue is UB, except through char and unsigned char lvalues. (See 3.10
Lvalues and rvalues / 15.)

rule 3: A program can reuse the storage of an object to create a new
object. It can do this by:
- Calling placement new on a piece of storage (though a subsequent
read will be UB if it reads uninitialized data [except (?) through
char or unsigned char lvalues]), or
- Writing to that storage through an lvalue of a type with trivial
initialization.

rule 4: [Note: An implementation may not move an access of an object
past an expression which ends the object's lifetime, such as an
expression which reuses the storage. Similarly, an implementation may
not move an access of an object before the expression which starts the
object's lifetime, such as placement new.]

I think the following examples illustrate my thought process.

We definitely need to allow:

 //example 1
 #include <cassert>
 #include <cstdlib>
 #include <iostream>
 #include <new>
 using namespace std;

 /*This is a very bad memory allocator.
 It only holds one block of a particular size.
 It's not thread safe.
 It has static initialization order problems.
 Let's just ignore that for now. */

 void* const block = malloc(sizeof(int));
 void* block2 = block;

 template <typename T> T* my_new()
 { if ( ! block2)
     throw std::bad_alloc();
   assert(block == block2);
   block2 = 0;
   return new(block) T;
 }

 template <typename T>
 void my_delete(T* p)
 { p->~T();
   assert( ! block2);
   assert(block == p);
   block2 = p;
 }

 //ok, let's use that user-written allocator
 struct T { T():x(1){} ~T(){} int x; };
 struct U { U():x(2){} ~U(){} short x; };
 int main()
 { T* t = my_new<T>();
   cout << t->x << endl;
   my_delete(t);
   U* u = my_new<U>();
   cout << u->x << endl;
 }

Ok. Let's see. Why does this work? Why can't the compiler reorder main
to the following?

 int main()
 { T* t = my_new<T>();
   my_delete(t);         //switched
   cout << t->x << endl; //switched
   U* u = my_new<U>();
   cout << u->x << endl;
 }

The compiler has two choices here. Either it can treat my_delete as
opaque, and it must assume that it can end the lifetime of *t, or it
sees the body of my_delete and it sees that my_delete does end the
lifetime of *t so it cannot reorder an access to after the end of the
lifetime. So, I think that with the above rules, we're good in terms
of basic malloc and free, and new and delete, usage.

I am a little concerned that it's not good enough with types with
trivial destructors, such as int. The lifetime of an int object would
continue to exist past a "my_delete" function call. With a single
threaded program and memory allocator, this would be allowed under the
"as-if" rule, but I'm concerned about multi-threaded programs and
thread-safe memory allocators now. Whatever synchronization in delete
would presumably (?) prevent the compiler from making the reordering
shown above, so we're still good.

So, let's move on to a more interesting example. We don't have to
allow the following per se. It might not be that bad if we don't.
However, I think too many people expect that the following ought to
work, so we probably ought to keep it working if possible.

 //example 2
 #include <cstdlib>
 #include <iostream>
 using namespace std;

 int main()
 { void* p = malloc(sizeof(int));
   int* x = reinterpret_cast<int*>(p);
   short* y = reinterpret_cast<short*>(p);
   *x = 1; //line 1
   cout << *x << endl;
   *y = 1; //line 2
   cout << *y << endl;
 }

Now consider a very slight twist to example 2. The difference is that
the cast is hidden in a different scope. I'm not sure what we want to
do with this at all.

 //example 3
 #include <cstdlib>
 #include <iostream>
 using namespace std;

 template <typename A, typename B>
   A cast(B b)
     { return reinterpret_cast<A>(b); }
 int main()
 { void* p = malloc(sizeof(int));
   int* x = cast<int*, void*>(p);
   short* y = cast<short*, void*>(p);
   *x = 1; //line 1
   cout << *x << endl;
   *y = 1; //line 2
   cout << *y << endl;
 }

But we definitely want to disallow:

 //example 4
 #include <cstdlib>
 #include <iostream>
 using namespace std;

 void foo(int* , short* );

 template <typename A, typename B>
   A cast(B b)
     { return reinterpret_cast<A>(b); }

 int main()
 { void* p = malloc(sizeof(int));
   int* x = cast<int*, void*>(p);
   short* y = cast<short*, void*>(p);
   *x = 1; //line 1
   foo(x, y);
 }

 void foo(int* x, short* y)
 { cout << *x << endl;
   *y = 1; //line 2
   cout << *y << endl;
 }

We need some rule which definitely allows example 1, preferably allows
example 2, don't know on example 3, and disallows example 4.

I was thinking that this would be so much simpler if we just required
that to reuse storage you must use placement-new, even for trivially
constructable types such as int. However, if we want to support
existing code which reuses storage by writing to that storage through
an int lvalue, then things get fun. I'm not sure where to start. I've
been thinking about this for a bit, and not making much headway.


--
[ comp.std.c++ is moderated.  To submit articles, try posting with your ]
[ newsreader.  If that fails, use mailto:std-cpp-submit@vandevoorde.com ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: "Johannes Schaub (litb)" <schaub-johannes@web.de>
Date: Tue, 30 Nov 2010 11:28:36 CST
Raw View
[I'm resending it - another reply of me a week ago didn't appear in the
usenet for some reason].

Joshua Maurice wrote:

>
> [I'm unable to reply directly to the OP post via google groups for
> some reason.]
>
> I have my own questions as well, if there would be people kind enough
> to answer them. I'm looking at the current wording at:
> http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#1116
>
> First, small question. I am curious how this is supposed to play out
> with malloc. Presumably "malloc(x)" is interchangable with "new
> char[x]" under this scheme (modulo char* vs void* expression type). Is
> there a way to create an object which has an associated "allocation
> type" besides "a new expression" and "a variable definition"? I
> haven't fully thought this through yet, but it's nagging me that we're
> treating new as special, but not a user defined allocation function,
> all while a new expression is often implemented largely in userland C+
> + code on top of malloc.
>

I've wondered too what obtaining storage for an object of a particular type
would mean and whether it wouldn't exclude malloc'ed storage and such, and
couldn't make up my mind about it.

> Second, I agree fully with the changed wording to 3.8 Object
> Lifetime / 1. That is a much needed fix. One quick question, are we
> sure that all types are exactly one of "has non-trivial
> initialization" or "trivially copyable"? Ex:
>  class T
>  {
>    T() : x(0) {}
>    int x;
>  };
> Type "T" has non-trivial initialization, correct? Type "T" is
> trivially copyable, correct? This seems to defeat the purpose of the
> proposed wording. What we need is wording that says the lifetime of an
> object with a non-trivial constructor begins once its constructor
> finishes (successfully), and the lifetime of an object with a trivial
> constructor begins once a write is made to the storage through an
> lvalue of that type. At least, something like that put into
> standardeze.
>

I like this. Clear and simple!

> Note that my phrasing is slightly different. Allow me to clarify it
> with some examples:
>
>
> Example:
>
>  struct T { int x; int y; };
>  int main()
>  {
>    char* c = new char[sizeof(T)];
>
>    T* t = reinterpret_cast<T*>(c);
>    /*Ok. No T object yet. */
>
>    t->x = 1;
>    /*So, do we have a T object or not? By the proposed wording, I
> think no. We haven't copied the object representation of a /full/ T
> object into the storage, so no T object exists. Thus a read "cout << t-
>>x" would be undefined behavior according to the strict aliasing rules
> of "3.10 Lvalues and rvalues / 15". I do not think that this is a good
> way to define the standard. I think that "cout << t->x" should be
> defined, and I that that "cout << t->y" should be undefined behavior
> because it reads uninitialized data, a normal use of that normal rule.
> */
>    t->y = 1;
>    /*Ok, we better have a T object at this point. */
>  }
>

I think I'm starting to change my opinion about the aliasing paragraph. One
might conceivably argue that "t->y = 1" does "use an lvalue of type T", it
seems.

Your description seems to assume (like I'm starting to do too) that the
access "t->x" will use two types to acces the stored value: lvalue of type
T, and lvalue of type int? If that is so, then it should comply to the
aliasing rule it seems: For the lvalue of type "T", we have an aggregate
type T that contains the int object as one of its members, and for the
"int"
lvalue it matches the (dynamic) type of that object.

> Third. Let's look at the example on the issue page.
>
>  int f() {
>    union U { double d; } u1, u2;
>    (int&)u1.d = 1;  //line 1
>    u2 = u1;  //line 2
>    return (int&)u2.d;
>  }
>
>>From my understanding of the C++03 standard, this is undefined
> behavior. At line 1, the write to the storage through the int lvalue
> reuses the storage, which ends the lifetime of all objects in that
> storage (if any), and starts the lifetime of a new int object. At line
> 2, it accesses the storage of u1 which contains an int object through
> a double lvalue. (An assignment of unions just assigns the first
> member right? If I'm wrong, no matter. int is not involved, so it
> doesn't change the rest of my reasoning.) This has undefined behavior
> because it doesn't satisfy any of the bullets in "3.10 Lvalues and
> rvalues / 15".
>

I thought this too and verified my suspicions, but I looked only into the
C++0 draft, which says "The implicitly-defined copy assignment operator for
a union X copies the object representation (3.9) of X.". It appears though
that C++03 in fact didn't state that.

I'm not sure how C++03 would handle an union with multiple declared
members.
The operator= in C++03 is said to "performs memberwise assignment of its
subobjects", which I don't know what it means for an union.

> However, I am still concerned that we have problems. Let's consider:
>
>  #include <iostream>
>  using namespace std;
>  int main()
>  { char* c = new char[sizeof(int) + sizeof(double)];
>    int* x = reinterpret_cast<int*>(c)
>    double* d = reinterpret_cast<double*>(c)
>    *x = 1;
>    cout << *x << endl;
>    *d = 1;
>    cout << *d << endl;
>  }
>
> As far as I can tell, this is still legal under the sane
> interpretation of C++03 (which includes my suggested revision of 3.8
> Object Lifetime / 1), and this is still legal under the proposed
> wording of issue 1116. Specifically, it would be illegal for a
> compiler to conclude that *x and *d do not alias, and change the above
> program to:
>
>  #include <iostream>
>  using namespace std;
>  int main()
>  { char* c = new char[sizeof(int) + sizeof(double)];
>    int* x = reinterpret_cast<int*>(c)
>    double* d = reinterpret_cast<double*>(c)
>    *x = 1;
>    *d = 1;
>    cout << *x << endl;
>    cout << *d << endl;
>  }
>
> Either the above is not a valid optimization, or we can't have generic
> user defined memory allocators in pure conforming C++.
>

I do believe too that this is not a valid optimization under C++03.


--
[ comp.std.c++ is moderated.  To submit articles, try posting with your ]
[ newsreader.  If that fails, use mailto:std-cpp-submit@vandevoorde.com ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: Joshua Maurice <joshuamaurice@gmail.com>
Date: Thu, 2 Dec 2010 12:16:59 CST
Raw View
On Nov 30, 9:28 am, "Johannes Schaub (litb)" <schaub-johan...@web.de>
wrote:
> Joshua Maurice wrote:
> > Second, I agree fully with the changed wording to 3.8 Object
> > Lifetime / 1. That is a much needed fix. One quick question, are we
> > sure that all types are exactly one of "has non-trivial
> > initialization" or "trivially copyable"? Ex:
> >  class T
> >  {
> >    T() : x(0) {}
> >    int x;
> >  };
> > Type "T" has non-trivial initialization, correct? Type "T" is
> > trivially copyable, correct? This seems to defeat the purpose of the
> > proposed wording. What we need is wording that says the lifetime of an
> > object with a non-trivial constructor begins once its constructor
> > finishes (successfully), and the lifetime of an object with a trivial
> > constructor begins once a write is made to the storage through an
> > lvalue of that type. At least, something like that put into
> > standardeze.
>
> I like this. Clear and simple!
>
>
>
> > Note that my phrasing is slightly different. Allow me to clarify it
> > with some examples:
>
> > Example:
>
> >  struct T { int x; int y; };
> >  int main()
> >  {
> >    char* c = new char[sizeof(T)];
>
> >    T* t = reinterpret_cast<T*>(c);
> >    /*Ok. No T object yet. */
>
> >    t->x = 1;
> >    /*So, do we have a T object or not? By the proposed wording, I
> > think no. We haven't copied the object representation of a /full/ T
> > object into the storage, so no T object exists. Thus a read "cout << t-
> >>x" would be undefined behavior according to the strict aliasing rules
> > of "3.10 Lvalues and rvalues / 15". I do not think that this is a good
> > way to define the standard. I think that "cout << t->x" should be
> > defined, and I that that "cout << t->y" should be undefined behavior
> > because it reads uninitialized data, a normal use of that normal rule.
> > */
> >    t->y = 1;
> >    /*Ok, we better have a T object at this point. */
> >  }
>
> I think I'm starting to change my opinion about the aliasing paragraph.
One
> might conceivably argue that "t->y = 1" does "use an lvalue of type T", it
> seems.
>
> Your description seems to assume (like I'm starting to do too) that the
> access "t->x" will use two types to acces the stored value: lvalue of type
> T, and lvalue of type int? If that is so, then it should comply to the
> aliasing rule it seems: For the lvalue of type "T", we have an aggregate
> type T that contains the int object as one of its members, and for the
> "int"
> lvalue it matches the (dynamic) type of that object.

I was thinking about this more, and I think I changed my mind. At
least, I pulled some slightly different rules out of ... that place.

An lvalue has two things: a type and an address, right? That's all an
lvalue is, right?

Consider the examples:

 //example 1
 struct T { int x; int y; };
 int main()
 { char* p = new char[sizeof(T)];
   T* t = reinterpret_cast<T*>(p);
   t->x = 1;
   t->y = 1;
 }

 //example 2
 #include <cassert>
 struct T { int x; int y; };
 struct U { int x; int y; };
 int main()
 { char* p = new char[sizeof(T)];
   T* t = reinterpret_cast<T*>(p);
   t->x = 1;
   t->y = 1;
   U* u = reinterpret_cast<U*>(p);
   assert( & t->y == & u->y);
 }

 //example 3
 #include <cassert>
 struct T { int x; int y; };
 struct U { int x; int y; };
 int main()
 { char* p = new char[sizeof(T)];
   T* t = reinterpret_cast<T*>(p);
   t->x = 1;
   t->y = 1;
   U* u = reinterpret_cast<U*>(p);
   return u->y;
 }

Ok. For the new C++0x standard, T and U are standard layout, right?
That means that the assert in example 2 will not be tripped, right?

Ok, we want example 1 to not have UB. We want example 2 to not have
UB, and we want the assert to not be tripped. Finally, I /think/ that
we want example 3 to have UB. Right? I'm actually unsure. I think we
want that. We want to say that the compiler may assume that "t->y"
does not alias "u->y", in other words that example 3 has undefined
behavior because of the "u->y" read. However, "t->y" is an lvalue of
type int, "u->y" is an lvalue of type int, and both lvalues refer to
the same address. Thus, if we want to find a violation of the
standard, it won't be found simply in "3.10 Lvalues and rvalues /
15".

I'm not sure how the C++03 standard disallows this, if at all. I was
looking over "5.2.5 Class member access". In "5.2.5 Class member
access / 1", it says that the left hand side is "evaluated". This
means that "u" is evaluated. I presume that evaluation of a name is a
no-op, so no UB can be found there. (Or am I wrong?) I don't know
where else it would be.

If we want to disallow example 3, the most straightforward way appears
to be in the class member access expression. (If it's not already
there) add a section which says:
 If "the object expression" refers to a piece of storage which does
not contain an object of the type of "the object expression", then the
program has undefined behavior.
I'm not sure if we want to actually add this though. Note that (if it
wasn't already there) this could break perverse pre-existing
conforming programs.

> > However, I am still concerned that we have problems. Let's consider:
>
> >  #include <iostream>
> >  using namespace std;
> >  int main()
> >  { char* c = new char[sizeof(int) + sizeof(double)];
> >    int* x = reinterpret_cast<int*>(c)
> >    double* d = reinterpret_cast<double*>(c)
> >    *x = 1;
> >    cout << *x << endl;
> >    *d = 1;
> >    cout << *d << endl;
> >  }
>
> > As far as I can tell, this is still legal under the sane
> > interpretation of C++03 (which includes my suggested revision of 3.8
> > Object Lifetime / 1), and this is still legal under the proposed
> > wording of issue 1116. Specifically, it would be illegal for a
> > compiler to conclude that *x and *d do not alias, and change the above
> > program to:
>
> >  #include <iostream>
> >  using namespace std;
> >  int main()
> >  { char* c = new char[sizeof(int) + sizeof(double)];
> >    int* x = reinterpret_cast<int*>(c)
> >    double* d = reinterpret_cast<double*>(c)
> >    *x = 1;
> >    *d = 1;
> >    cout << *x << endl;
> >    cout << *d << endl;
> >  }
>
> > Either the above is not a valid optimization, or we can't have generic
> > user defined memory allocators in pure conforming C++.
>
> I do believe too that this is not a valid optimization under C++03.

I was a little too strong when I said "Either the above is not a valid
optimization, or we can't have generic user defined memory allocators
in pure conforming C++." I think that if we require all reuse of
storage to be with placement-new, then it might all work out
relatively sanely. I simply assumed that that wasn't an option, which
presumably it isn't. We probably need to support creating an object by
writing to the storage through an lvalue of type with trivial
construction. Once we have that, we're stuck with some fun problems.

Problem 1- the union DR. Does anyone know what's going on with the
union DR?

Problem 2-
 struct T { int x; int y; };
 struct U { int x; int y; };
 int main()
 { char* p = new char[sizeof(T)];
   T* t = reinterpret_cast<T*>(p);
   int* x = & t->x;
   int* y = & t->y;
   *x = 1;
   *y = 1;
 }
Do we have an object of type T in this program? When does it exist?

One plausible argument goes: "The write via 't->x' creates a new /
complete/ object of type int. The write via 't->y' creates a new /
complete/ object of complete type int. I don't see how this creates
another complete object of type T."

Another plausible argument goes: "The write via 't->x' creates a new /
complete/ object of type int. The write via 't->y' creates a new /
complete/ object of complete type int. After the second write, which
created all of the sub-objects of the type T which is trivially
constructable, then we magically get a complete object of dynamic type
T, and the two complete type int objects magically become sub-
objects." However, what's to favor "T" instead of "U"? Do we have two
complete objects at the same piece of memory at the same time? I think
we really want to avoid that conclusion.

I don't think either of those suffice. I just thought up the
following. It seems reasonable, and solves Problem 2 at least. The
union DR remains unaddressed (no pun intended).

1- For all fundamental types T, for all reads through an expression of
possibly cv-qualified type T, if there is no live object (possibly sub-
object) with that exact address and with exact type T or cv-
unqualified type T, then the program has UB. Except for char, unsigned
char, and cv-qualified versions - reads through those types can read
from storage where there is no object, and can read from storage where
there is an unrelated object or objects.

2- For all fundamental types T, for all writes through an expression
of possibly cv-qualified type T, if there is no live object (possibly
sub-object) with that exact address and with exact type T or cv-
unqualified type T, then all objects (if any) in that segment of
memory and all of their sub-objects have their lifetimes ended, and a
new object of type T is created in the segment of memory. Except for
char, unsigned char, and cv-qualified versions - writes through those
types do not end the lifetime of objects, and they do not start
lifetimes of new objects.

3- For all class types T with trivial initialization, for all member
access expressions with "object expression" of possibly cv-qualified
type T, if there is no live object (possibly sub-object) with that
exact address and with exact type T or cv-unqualified type T, all
objects (if any) in that segment of memory and all of their sub-
objects have their lifetimes ended, and a new complete object of
dynamic type T is created in the segment of memory (including all sub-
objects [as normal]).

4- All of the following are "read/write class accesses".
- A member access expression.
- A cast (possibly implicit) to a virtual base class.
- A cast to a virtually-derived class.
- A dynamic_cast.
[Others which I'm neglecting.]

For a possibly cv-qualified class type T, except for member access
expressions on types with trivial initialization, a "read/write class
access" on an expression of type T is UB if there is no live object
(possibly sub-object) with that exact address and with exact type T or
cv-unqualified type T.

5- A placement-new expression ends the lifetime of all objects (if
any) in that segment of memory and all of their sub-objects. It starts
the lifetime of a new complete object in that segment of memory.

[Insert stuff for new expressions, delete expressions, free, and other
required stuffs.]

--

The goal is:

 struct T { int x; int y; };
 struct U { int x; int y; };
 int main()
 { char* p = new char[sizeof(T)];

   T* t = reinterpret_cast<T*>(p); /* No T object yet. */

   int* x = & t->x; /* Ok. We just had a member access expression
with a class type with trivial initialization. If there wasn't already
an object (possibly sub-object) of possibly cv-unqualified type T at
that exact address (which there wasn't), then we just ended the
lifetime of all objects in that segment of memory (the char array)
(which we just did), and we just created a new complete object of
dynamic type T (which we just did). */

   int* y = & t->y; /* An object of exact type T exists at the exact
address of t, so all is well. No objects are created or destroyed in
this expression.*/

   *x = 1; /* An object of type T exists at that address, and x
specifically points to the T::x sub-object, so all is well. No objects
are created or destroyed in this expression. */

   *y = 1; /* As above. */
 }

Thus, we don't have a U object at that piece of memory, so the
compiler is free to assume that U pointers do not alias T pointers.
(Well, until we get around to solving the union DR.)

My rules have the potentially unfortunate side effect of making the
following undefined:
 struct T { int x; int y; };
 int main()
 { char* p = new char[sizeof(T)];
   * reinterpret_cast<int*>(p) = 1;
   return reinterpret_cast<T*>(p) -> x;
 }
In the above program, the write through the int type creates a new
complete int object. Next, the class member access is on memory which
does not contain a T object, so it destroys all objects in that
memory, aka the int object just created. The return expression does a
read of T::x, which does not violate the aliasing rule, but it would
violate the rule about reading uninitialized data. At the point of the
return expression, the sub-object T::x has not yet been written since
its lifetime began, so the read is UB.


--
[ comp.std.c++ is moderated.  To submit articles, try posting with your ]
[ newsreader.  If that fails, use mailto:std-cpp-submit@vandevoorde.com ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: Joshua Maurice <joshuamaurice@gmail.com>
Date: Sat, 4 Dec 2010 01:50:05 CST
Raw View
On Dec 2, 10:16 am, Joshua Maurice <joshuamaur...@gmail.com> wrote:
> Problem 2-
>  struct T { int x; int y; };
>  struct U { int x; int y; };
>  int main()
>  { char* p = new char[sizeof(T)];
>    T* t = reinterpret_cast<T*>(p);
>    int* x = & t->x;
>    int* y = & t->y;
>    *x = 1;
>    *y = 1;
>  }
> Do we have an object of type T in this program? When does it exist?
>
> One plausible argument goes: "The write via 't->x' creates a new /
> complete/ object of type int. The write via 't->y' creates a new /
> complete/ object of complete type int. I don't see how this creates
> another complete object of type T."
>
> Another plausible argument goes: "The write via 't->x' creates a new /
> complete/ object of type int. The write via 't->y' creates a new /
> complete/ object of complete type int. After the second write, which
> created all of the sub-objects of the type T which is trivially
> constructable, then we magically get a complete object of dynamic type
> T, and the two complete type int objects magically become sub-
> objects." However, what's to favor "T" instead of "U"? Do we have two
> complete objects at the same piece of memory at the same time? I think
> we really want to avoid that conclusion.
>
> I don't think either of those suffice. I just thought up the
> following. It seems reasonable, and solves Problem 2 at least. The
> union DR remains unaddressed (no pun intended).
>
> 1- For all fundamental types T, for all reads through an expression of
> possibly cv-qualified type T, if there is no live object (possibly sub-
> object) with that exact address and with exact type T or cv-
> unqualified type T, then the program has UB. Except for char, unsigned
> char, and cv-qualified versions - reads through those types can read
> from storage where there is no object, and can read from storage where
> there is an unrelated object or objects.
>
> 2- For all fundamental types T, for all writes through an expression
> of possibly cv-qualified type T, if there is no live object (possibly
> sub-object) with that exact address and with exact type T or cv-
> unqualified type T, then all objects (if any) in that segment of
> memory and all of their sub-objects have their lifetimes ended, and a
> new object of type T is created in the segment of memory. Except for
> char, unsigned char, and cv-qualified versions - writes through those
> types do not end the lifetime of objects, and they do not start
> lifetimes of new objects.
>
> 3- For all class types T with trivial initialization, for all member
> access expressions with "object expression" of possibly cv-qualified
> type T, if there is no live object (possibly sub-object) with that
> exact address and with exact type T or cv-unqualified type T, all
> objects (if any) in that segment of memory and all of their sub-
> objects have their lifetimes ended, and a new complete object of
> dynamic type T is created in the segment of memory (including all sub-
> objects [as normal]).
>
> 4- All of the following are "read/write class accesses".
> - A member access expression.
> - A cast (possibly implicit) to a virtual base class.
> - A cast to a virtually-derived class.
> - A dynamic_cast.
> [Others which I'm neglecting.]
>
> For a possibly cv-qualified class type T, except for member access
> expressions on types with trivial initialization, a "read/write class
> access" on an expression of type T is UB if there is no live object
> (possibly sub-object) with that exact address and with exact type T or
> cv-unqualified type T.
>
> 5- A placement-new expression ends the lifetime of all objects (if
> any) in that segment of memory and all of their sub-objects. It starts
> the lifetime of a new complete object in that segment of memory.
>
> [Insert stuff for new expressions, delete expressions, free, and other
> required stuffs.]
>
> --
>
> The goal is:
>
>  struct T { int x; int y; };
>  struct U { int x; int y; };
>  int main()
>  { char* p = new char[sizeof(T)];
>
>    T* t = reinterpret_cast<T*>(p); /* No T object yet. */
>
>    int* x = & t->x; /* Ok. We just had a member access expression
> with a class type with trivial initialization. If there wasn't already
> an object (possibly sub-object) of possibly cv-unqualified type T at
> that exact address (which there wasn't), then we just ended the
> lifetime of all objects in that segment of memory (the char array)
> (which we just did), and we just created a new complete object of
> dynamic type T (which we just did). */
>
>    int* y = & t->y; /* An object of exact type T exists at the exact
> address of t, so all is well. No objects are created or destroyed in
> this expression.*/
>
>    *x = 1; /* An object of type T exists at that address, and x
> specifically points to the T::x sub-object, so all is well. No objects
> are created or destroyed in this expression. */
>
>    *y = 1; /* As above. */
>  }
>
> Thus, we don't have a U object at that piece of memory, so the
> compiler is free to assume that U pointers do not alias T pointers.
> (Well, until we get around to solving the union DR.)
>
> My rules have the potentially unfortunate side effect of making the
> following undefined:
>  struct T { int x; int y; };
>  int main()
>  { char* p = new char[sizeof(T)];
>    * reinterpret_cast<int*>(p) = 1;
>    return reinterpret_cast<T*>(p) -> x;
>  }
> In the above program, the write through the int type creates a new
> complete int object. Next, the class member access is on memory which
> does not contain a T object, so it destroys all objects in that
> memory, aka the int object just created. The return expression does a
> read of T::x, which does not violate the aliasing rule, but it would
> violate the rule about reading uninitialized data. At the point of the
> return expression, the sub-object T::x has not yet been written since
> its lifetime began, so the read is UB.

Don't mind me too much. I'm just thinking aloud. When I later thought
about making member access expressions create objects, it seemed quite
silly.

Also, a rather determined coder could use offset_of of to accomplish
the same result without a member access expression. What do we do in
that case? I may be operating off a false presupposition. Let me ask
this:

 struct T { int x; int y; };
 struct U { int x; int y; };
 void foo(T* t, U* u)
 {
   t->y = 1;
   t->x = t->y + 3;
   u->y = 2;
   u->x = u->y + 3;
 }

Do we want to guarantee that foo does the naive thing, even if "t" and
"u" alias? What do actual modern compilers do? Does anyone want to
give an allowance to the compilers to assume that "t" and "u" don't
alias? Do we want to allow compilers to rewrite the above to the
following?

 struct T { int x; int y; };
 struct U { int x; int y; };
 void foo(T* t, U* u)
 {
   t->y = 1;
   u->y = 2;
   t->x = t->y + 3;
   u->x = u->y + 3;
 }

I don't see a straightforward sensible way to allow the above
reordering (which I /think/ that "the people" with the strict aliasing
rules want to do), while allowing the /very common/ idiom in C of
(implicitly) casting the result of malloc to a struct type, then
writing to its various members.

 // C89 code
 #include <stdlib.h>
 struct T { int x; int y; };
 int main()
 { struct T* t = malloc(sizeof(struct T));
   t->x = 1;
   t->y = 2;
   return t->x + t->y;
 }

The worst part is I don't see a sensible way to discriminate based on
the class type. The accesses themselves are made through lvalues of
the sub-objects, which can be obtained through obscure means like
offset_of and such.

Speaking of which, when exactly can we speak of a read or a write
through an lvalue of a class type? Copy construction or operator=? For
a C-type, I would argue that it's simply member-wise copying, which
eventually resolves down to only writes through fundamental types. The
only things offhand which are legitimately reads or writes through a
class type are:
- constructors (including copy constructors), assignments, and
destructors of classes with virtual functions or virtual bases, and
- virtual function calls, and
- casts (including implicit casts) to virtual base classes, casts to
virtually derived classes, and dynamic_cast, and
- typeid.
Without those, any access of a class type will eventually resolve down
to an access through a fundamental type. This situation exists in C,
which is possibly why it works (sort of) in C. For C, I could pull out
a rule which says that once a piece of memory has been malloc-ed, you
may read and write to it via C-types as much as you want, as long as
you reads and writes through types which are consistent with a single
complete object C-type (or char or unsigned char writes).

That "consistency rule" solves one of our problems (I think), but it
doesn't solve the union DR problem. If we could start from scratch, we
might even be able to solev the union DR in a sensible way. You could
require some special expression which separates the end of the
lifetime of one object and the start of the lifetime of another object
which reuses the storage (such as placement-new, or malloc and free
calls, new and delete calls, and so on).

However, I don't see a sensible way to do it and to support the
already voluminous pre-existing C and C++ code, such as all memory
allocators which reuse storage. This includes AFAIK most memory
allocators ever written in C and C++, such as all libc mallocs (which
are largely written in user space in terms of system calls which
allocate virtual pages), the Hoard Allocator, and so on.

I'm marginally surprised this big house of cards hasn't fallen over
yet. It's my guess that compiler writers either don't take advantage
of the strict aliasing rules for optimization (such as windows AFAIK),
or they take the gcc approach where they have presumably fragile and
AFAIK undocumented rules for determining when sufficiently different
types may alias.

As a guess for the house of cards metaphor, I suspect that having
malloc implemented as a shared lib call makes optimization around it
difficult, so users are saved there. I also guess that users who
implement their own memory allocators are very careful to ensure that
when they reuse storage, there is a malloc-like call and a free-like
call which are so complex as to inhibit any cool compiler
optimizations. For the couple rare cases which do crazy things like:
 void foo(int* a, short* b) { *a = 1; *b = 2; }
where a and b do alias, they work out of luck, or they are careful and
are still saved by luck - specifically by undocumented alias analysis
which compilers like gcc do.


--
[ comp.std.c++ is moderated.  To submit articles, try posting with your ]
[ newsreader.  If that fails, use mailto:std-cpp-submit@vandevoorde.com ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: =?ISO-8859-1?Q?Daniel_Kr=FCgler?= <daniel.kruegler@googlemail.com>
Date: Wed, 15 Sep 2010 23:04:52 CST
Raw View
On 16 Sep., 01:26, "Johannes Schaub (litb)" <schaub-johan...@web.de>
wrote:
> I just read issue #1116 and have a comment about the change to 3.8
> [basic.life] paragraph 4 . I believe that the added text renders the defa=
ult
> to undefined behavior:
>
> union A { int a; double b; } u;
> u.b = 0.0;
> u.a = 0;
>
> First we obtain storage of for object of type "double" by copying the obj=
ect
> representation of "0.0" into the union member object. We then use the
> storage for an object of type "int", but reading afterwards as a double i=
s
> undefined behavior.

I'm not sure what your intention is. Yes, the new wording should
have this effect. Are you implying that it doesn't say that or
that the new wording breaks this principle?

Greetings from Bremen,

Daniel Kr=FCgler



--
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use
mailto:std-c++@netlab.cs.rpi.edu<std-c%2B%2B@netlab.cs.rpi.edu>
]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: "Johannes Schaub (litb)" <schaub-johannes@web.de>
Date: Sun, 19 Sep 2010 10:07:04 CST
Raw View
Daniel Kr   gler wrote:

> On 16 Sep., 01:26, "Johannes Schaub (litb)" <schaub-johan...@web.de>
> wrote:
>> I just read issue #1116 and have a comment about the change to 3.8
>> [basic.life] paragraph 4 . I believe that the added text renders the
>> [defa=
> ult
>> to undefined behavior:
>>
>> union A { int a; double b; } u;
>> u.b = 0.0;
>> u.a = 0;
>>
>> First we obtain storage of for object of type "double" by copying the
>> obj=
> ect
>> representation of "0.0" into the union member object. We then use the
>> storage for an object of type "int", but reading afterwards as a double
>> i=
> s
>> undefined behavior.
>
> I'm not sure what your intention is. Yes, the new wording should
> have this effect. Are you implying that it doesn't say that or
> that the new wording breaks this principle?
>

The problem is that it's not just undefined behavior if you do an actual
read of the double, but it's undefined behavior if the double read *would
be* undefined. Let me quote it so it's clear what I refer to:

"If a program obtains storage for an object of a particular type A (e.g.
with a variable definition or new-expression) and later reuses that storage
for an object of another type B such that accessing the stored value of the
B object through a glvalue of type A would have undefined behavior (3.10
[basic.lval]), the behavior is undefined."

It also makes it undefined for other cases that are valid currently:

struct A { double a; };
struct B { int b; };

void *p = malloc(sizeof (A) + sizeof(B));
A *a = (A*)p;
*a = A();
 // obtained storage for an object of type A.

B *b = (B*)p;
*b = B();
 // reuse storage for an object of type B
 // -> but this is already UB under above wording!

I believe that the wording does not intend to make this and the union
example above undefined - in both cases we never touch the storage with the
other type again, unlike the code of that DR that in fact did.

--
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@netlab.cs.rpi.edu]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: =?ISO-8859-1?Q?Daniel_Kr=FCgler?= <daniel.kruegler@googlemail.com>
Date: Sun, 19 Sep 2010 18:17:03 CST
Raw View
On 19 Sep., 18:07, "Johannes Schaub (litb)" <schaub-johan...@web.de>
wrote:
[..]
> The problem is that it's not just undefined behavior if you do an actual
> read of the double, but it's undefined behavior if the double read *would
> be* undefined. Let me quote it so it's clear what I refer to:
>
> "If a program obtains storage for an object of a particular type A (e.g.
> with a variable definition or new-expression) and later reuses that storage
> for an object of another type B such that accessing the stored value of the
> B object through a glvalue of type A would have undefined behavior (3.10
> [basic.lval]), the behavior is undefined."
>
> It also makes it undefined for other cases that are valid currently:
>
> struct A { double a; };
> struct B { int b; };
>
> void *p = malloc(sizeof (A) + sizeof(B));
> A *a = (A*)p;
> *a = A();
>  // obtained storage for an object of type A.
>
> B *b = (B*)p;
> *b = B();
>  // reuse storage for an object of type B
>  // -> but this is already UB under above wording!
>
> I believe that the wording does not intend to make this and the union
> example above undefined - in both cases we never touch the storage with the
> other type again, unlike the code of that DR that in fact did.

Actually the wording *does* intend to make both examples
undefined behavior.

The purpose of the aliasing rules in sub-clause [basic.lval]
is to limit the scope of what an optimizer has to consider
in deciding whether a given object still has a known value
at a given point. If there are no write attempts via an lvalue
of one of the compatible types between point A and point B
in the execution, the optimizer is required to assume that
an object has the same value at point B that it had at point
A.

Lets look at your example shown above: If the optimizer
knows that a->a is initialized with the result of some
expensive calculation, and if an equivalent expression
appears at some point after b->b is stored, an optimizer
is supposed to assume that it can simply read the
value of a->a for the value of that expression instead
of doing the complete calculating from scratch, *even
though* it has been overwritten by the initialization of
b->b, because the write was done via an lvalue of a
type that the optimizer is permitted to assume not to
alias a->a. This is the reason why it is undefined
behavior: it allows optimizations in "reasonable"
programs that would be forbidden if constructs like
the above were well-defined. The undefined behavior
allows the implementation to get the "wrong" answer
"legally" when the program does something like the
above that an optimizer cannot be expected to track.

Note that above schematic description does not
require that the first type interpretation of the memory
is still "in use". Consider a variation of your example
like the following:

void* p = malloc(sizeof(double) + sizeof(int));
double* pd = (double*) p;
*pd = sqrt(2.0) * sqrt(7.0);
int* pi = (int*) p;
*pi = 13;
double val = sqrt(2.0) * sqrt(7.0);

Here, a compiler is allowed to assume that the value
of sqrt(2.0) * sqrt(7.0) does still exist in *pd and that
it can just read that value from this location at the
point were val is initialized with the same value, because
it is also allowed to assume that no latter write attempts
have been done to this location. That's the whole point
of the aliasing rules in [basic.lval]: Data flow analysis
in anything more than toy programs is essentially
useless if every pointer dereference must be assumed
to be a possible alias to everything, independent of its
type.

HTH & Greetings from Bremen,

Daniel Kr   gler

--
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@netlab.cs.rpi.edu]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: "Johannes Schaub (litb)" <schaub-johannes@web.de>
Date: Sun, 19 Sep 2010 21:19:17 CST
Raw View
Daniel Kr   gler wrote:

> On 19 Sep., 18:07, "Johannes Schaub (litb)" <schaub-johan...@web.de>
> wrote:
> [..]
>> The problem is that it's not just undefined behavior if you do an actual
>> read of the double, but it's undefined behavior if the double read *would
>> be* undefined. Let me quote it so it's clear what I refer to:
>>
>> "If a program obtains storage for an object of a particular type A (e.g.
>> with a variable definition or new-expression) and later reuses that
>> storage for an object of another type B such that accessing the stored
>> value of the B object through a glvalue of type A would have undefined
>> behavior (3.10
>> [basic.lval]), the behavior is undefined."
>>
>> It also makes it undefined for other cases that are valid currently:
>>
>> struct A { double a; };
>> struct B { int b; };
>>
>> void *p = malloc(sizeof (A) + sizeof(B));
>> A *a = (A*)p;
>> *a = A();
>>  // obtained storage for an object of type A.
>>
>> B *b = (B*)p;
>> *b = B();
>>  // reuse storage for an object of type B
>>  // -> but this is already UB under above wording!
>>
>> I believe that the wording does not intend to make this and the union
>> example above undefined - in both cases we never touch the storage with
>> the other type again, unlike the code of that DR that in fact did.
>
> Actually the wording *does* intend to make both examples
> undefined behavior.
>

I believe we are not talking about the same thing, so let me restate it. My
initial example can be equivalently taken from the example of 9.5/5 in n3126
which just contains the same code with different names and types. Do you say
that that example now is undefined behavior with #1116 applied and that such
is intended?

There simply is no aliasing happening of the "char const*" object by an
lvalue of type "int". So why should it be undefined like #1116 requires and
you seem to state?

> The purpose of the aliasing rules in sub-clause [basic.lval]
> is to limit the scope of what an optimizer has to consider
> in deciding whether a given object still has a known value
> at a given point. If there are no write attempts via an lvalue
> of one of the compatible types between point A and point B
> in the execution, the optimizer is required to assume that
> an object has the same value at point B that it had at point
> A.
>

I agree.

> Lets look at your example shown above: If the optimizer
> knows that a->a is initialized with the result of some
> expensive calculation, and if an equivalent expression
> appears at some point after b->b is stored, an optimizer
> is supposed to assume that it can simply read the
> value of a->a for the value of that expression instead
> of doing the complete calculating from scratch, *even
> though* it has been overwritten by the initialization of
> b->b, because the write was done via an lvalue of a
> type that the optimizer is permitted to assume not to
> alias a->a.
>

By writing a value of "B", the object's type changed to B because we reused
its memory (see 3.8/4). Thus we are definitely allowed to use members of "b"
and write to them, and the compiler is not allowed to assume that "a->a" is
still valid, because that object has ended lifetime already.

I thought that's the whole point of how an union: Writing into one member
makes that member active by starting its lifetime and stopping the lifetime
of others by reusing their memory. It's all the reason the following is
undefined behavior:

union A { int x; double y; };
A a;
a.x = 0;
 // member object now has type 'int'

double d = a.y;
 // there this is undefined according to 3.10/15.
 // It tries to read an int by an lvalue of type double

a.y = 0.0;
 // but now the member object has type 'double'. It's
 // not an int anymore!


Do you really say that if we take the double read out ("double d = a.y;"),
the behavior is *still* undefined? What the whole point of an union then? I
once again think we are talking about different things.

> Note that above schematic description does not
> require that the first type interpretation of the memory
> is still "in use". Consider a variation of your example
> like the following:
>
> void* p = malloc(sizeof(double) + sizeof(int));
> double* pd = (double*) p;
> *pd = sqrt(2.0) * sqrt(7.0);
> int* pi = (int*) p;
> *pi = 13;
> double val = sqrt(2.0) * sqrt(7.0);
>
> Here, a compiler is allowed to assume that the value
> of sqrt(2.0) * sqrt(7.0) does still exist in *pd and that
> it can just read that value from this location at the
> point were val is initialized with the same value, because
> it is also allowed to assume that no latter write attempts
> have been done to this location.
>

I do not believe this to be true. The aliasing rules of 3.10/15 are
formulated in terms of dynamic types of objects, which can change according
to the lifetime starting and stopping sequences. Like in an union, in "*pi =
13", the object that is written to has type int just as the object "*pd =
..." referred to had type "double" when its value were being set. The
lifetime of the double object ended when its memory was being reused by the
int write (3.8/1 and 3.8/4).

This is subject of issue report #636. Sadly C++03 did not clearly state in
3.8 that "*pd = ..." created a double object and that "*pi = ..." did create
an int object. One had to conclude such semantics from the semantics of
union member writes and assume that an union "just works, so 3.8 has to have
this semantics.". I think it's therefor a good thing that #1116 clears it up
when exactly for types without a nontrivial constructor lifetime starts.

But it can't be its intention to make completely legal uses of unions like
my initial example or the one of 9.5/5 undefined, can it? Its intention was
to render US27 comment and similar undefined only.


--
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@netlab.cs.rpi.edu]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: =?ISO-8859-1?Q?Daniel_Kr=FCgler?= <daniel.kruegler@googlemail.com>
Date: Thu, 30 Sep 2010 01:55:24 CST
Raw View
[4th posting attempt within the last 8 days...]

On Sep 20, 5:19 am, "Johannes Schaub (litb)" <schaub-johan...@web.de>
wrote:
> I believe we are not talking about the same thing, so let me restate it. My
> initial example can be equivalently taken from the example of 9.5/5 in n3126
> which just contains the same code with different names and types. Do you say
> that that example now is undefined behavior with #1116 applied and that such
> is intended?

I don't believe that the current wording makes the
example in the WP undefined behaviour. It should
be explicitly allowed by the lvalue-via-union-rule in
the bulleted list of 3.10. It is different from your
example, though, which I repeat here for complete-
ness and to be sure that we are speaking of the
same thing:

union A { int a; double b; } u;
u.b = 0.0;
u.a = 0;
// Up to this point everything is fine
double d = u.b; // This is your addition - at least implied
// by the text you have added to the example.

Since you discuss this example below, I proceed there.

> There simply is no aliasing happening of the "char const*" object by an
> lvalue of type "int". So why should it be undefined like #1116 requires and
> you seem to state?

The reason is not because of the aliasing, see below.

> I thought that's the whole point of how an union: Writing into one member
> makes that member active by starting its lifetime and stopping the lifetime
> of others by reusing their memory. It's all the reason the following is
> undefined behavior:
>
> union A { int x; double y; };
> A a;
> a.x = 0;
>  // member object now has type 'int'
>
> double d = a.y;
>  // there this is undefined according to 3.10/15.
>  // It tries to read an int by an lvalue of type double
>
> a.y = 0.0;
>  // but now the member object has type 'double'. It's
>  // not an int anymore!
>
> Do you really say that if we take the double read out ("double d = a.y;"),
> the behavior is *still* undefined? What the whole point of an union then? I
> once again think we are talking about different things.

The behaviour is indeed intended to be undefined for
this particular example after the P/R of 1116 has been
applied. But it is *not* so, because a.y is an alias
violation under 3.10 (this would be an acceptable
alias, because it's via a union type which has a
double as one of its non-static data members).
Instead, it's undefined behavior because the lifetime
of a.y has *not* started, and the lvalue-to-rvalue
conversion on a.y is not one of the acceptable uses
of an lvalue referring to an out-of-lifetime object (see
3.8/6).

The pointer examples that you presented are aliasing
violations under 3.10, though, and making them UB is
the intent of the issue 1116 resolution. In fact, the
example added as part of this resolution:

int i;
(double&) i = 1.0;

is equivalent to these cases, because it is by definition
the same as:

int i;
*(double*) &i = 1.0;

The only difference is, that in our two other pointer
examples the pointer is explicit.

The intent definitely was to make the simple overwriting
of the object with an object of a non-aliasable type UB,
independent of whether the program later attempts to
access the object through its original type or not.

Note that it was particular important to still allow for
allocating char arrays and creating objects of some
arbitrary type within them. This has to be well-defined
and this is the reason why the following new part had
been added as part of the P/R (emphasis mine):

"such that accessing the stored value of the B object
through a glvalue of type A **would** have undefined
behavior"

HTH & Greetings from Bremen,

Daniel Kr   gler

--
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@netlab.cs.rpi.edu]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: "Johannes Schaub (litb)" <schaub-johannes@web.de>
Date: Wed, 15 Sep 2010 17:26:01 CST
Raw View
I just read issue #1116 and have a comment about the change to 3.8
[basic.life] paragraph 4 . I believe that the added text renders the default
to undefined behavior:

union A { int a; double b; } u;
u.b = 0.0;
u.a = 0;

First we obtain storage of for object of type "double" by copying the object
representation of "0.0" into the union member object. We then use the
storage for an object of type "int", but reading afterwards as a double is
undefined behavior.


--
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use
mailto:std-c++@netlab.cs.rpi.edu<std-c%2B%2B@netlab.cs.rpi.edu>
]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]