Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Is the aliasing rule symmetric?

74 views
Skip to first unread message

Johannes Schaub (litb)

unread,
Jan 21, 2011, 8:46:52 AM1/21/11
to
Posting my SO question to usenet:

Hello all. I had a discussion with someone on IRC and this question turned
up. We are allowed by the Standard to change an object of type `int` by a
`char` lvalue.

int a;
char *b = (char*) &a;
*b = 0;

Would we be allowed to do this in the opposite direction, if we know that
the alignment is fine?

The issue I'm seeing is that the aliasing rule does not cover the simple
case of the following, if one considers the aliasing rule as a non-symmetric
relation

int a;
a = 0;

The reason is, that each object contains a sequence of `sizeof(obj)`
`unsigned char` objects (called the "object representation"). If we change
the `int`, we will change some or all of those objects. However, the
aliasing rule only states we are allowed to change a `int` by an `char` or
`unsigned char`, but not the other way around. Another example

int a[1];
int *ra = a;
*ra = 0;

Only one direction is described by 3.10/15 ("An aggregate or union type that
includes..."), but this time we need the other way around ("A type that is
the element or non-static data member type of an aggregate...").

Is the other direction implied? The above spec quotes are from C++, but I
beleive same things apply to C.

SG

unread,
Jan 21, 2011, 10:18:51 AM1/21/11
to
On 21 Jan., 14:46, Johannes Schaub (litb) wrote:
>
> Hello all. I had a discussion with someone on IRC and this question turned
> up. We are allowed by the Standard to change an object of type `int` by a
> `char` lvalue.
>
>     int a;
>     char *b = (char*) &a;
>     *b = 0;
>
> Would we be allowed to do this in the opposite direction, if we know that
> the alignment is fine?

I think the question boils down to another question: When does a POD
object start to exist? One could argue that once you have properly
aligned memory to hold an int object and the byte sequence represents
a valid state of an int object, there *is* an int object. Consider

int* p = (int*)malloc(sizeof int);
*p = 1729;

malloc returns properly aligned uninitialized memory. When does the
int object start to exist?

Under the assumption that an int objects exists at some address as
soon as the byte sequence starting at that address represents a valid
int object's state and the memory is properly aligned, the aliasing
rule is practically symmetric.

Cheers!
SG

Ben Bacarisse

unread,
Jan 21, 2011, 11:15:05 AM1/21/11
to
"Johannes Schaub (litb)" <schaub-...@web.de> writes:

> Posting my SO question to usenet:

I don't find this question all that clear, but perhaps my confusion will
help make it clearer!

> Hello all. I had a discussion with someone on IRC and this question turned
> up. We are allowed by the Standard to change an object of type `int` by a
> `char` lvalue.
>
> int a;
> char *b = (char*) &a;
> *b = 0;

OK so far.

> Would we be allowed to do this in the opposite direction, if we know that
> the alignment is fine?

What's the opposite direction? Are you asking if changing the int will
change the value of *b? If so, yes (provided the new int value's bits
do indeed affect the byte in question).

If you mean can we change an array of char (suitable aligned) by writing
though and int *, then the answer is no.

> The issue I'm seeing is that the aliasing rule does not cover the simple
> case of the following, if one considers the aliasing rule as a non-symmetric
> relation
>
> int a;
> a = 0;

The wording in the C standard covers that case explicitly:

An object shall have its stored value accessed only by an lvalue
expression that has one of the following types:

-- a type compatible with the effective type of the object,
-- (etc...)

'a' is an lvalue expression compatible with the effective type of the
object denoted by 'a'.

C++ does not use the concept of effective type and it does not used C's
notion of compatible types, so there are four clauses in 3.10 p15 that
capture the same semantics as this one clause in the C standard.

> The reason is, that each object contains a sequence of `sizeof(obj)`
> `unsigned char` objects (called the "object representation"). If we change
> the `int`, we will change some or all of those objects. However, the
> aliasing rule only states we are allowed to change a `int` by an `char` or
> `unsigned char`, but not the other way around. Another example
>
> int a[1];
> int *ra = a;
> *ra = 0;
>
> Only one direction is described by 3.10/15 ("An aggregate or union type that
> includes..."), but this time we need the other way around ("A type that is
> the element or non-static data member type of an aggregate...").

I may be missing your point, but this case seems to me perfectly well
covered by the 6.5 p7 in C and 3.10 p15 in C++. The effective type of
*ra is int and that is compatible (actually the same as) the effective
type of the object being accessed. In C++ the access is through an
lvalue expression with the dynamic type of the object (both are int).

> Is the other direction implied? The above spec quotes are from C++, but I
> beleive same things apply to C.

--
Ben.

Johannes Schaub (litb)

unread,
Jan 21, 2011, 12:20:57 PM1/21/11
to
Ben Bacarisse wrote:

> "Johannes Schaub (litb)" <schaub-...@web.de> writes:
>
>> Would we be allowed to do this in the opposite direction, if we know that
>> the alignment is fine?
>
> What's the opposite direction? Are you asking if changing the int will
> change the value of *b? If so, yes (provided the new int value's bits
> do indeed affect the byte in question).
>

I mean to ask: If aliasing of an A object by an lvalue of type B is OK, is
aliasing of a B object by an lvalue of type A OK?

> If you mean can we change an array of char (suitable aligned) by writing
> though and int *, then the answer is no.
>
>> The issue I'm seeing is that the aliasing rule does not cover the simple
>> case of the following, if one considers the aliasing rule as a
>> non-symmetric relation
>>
>> int a;
>> a = 0;
>
> The wording in the C standard covers that case explicitly:
>
> An object shall have its stored value accessed only by an lvalue
> expression that has one of the following types:
>
> -- a type compatible with the effective type of the object,
> -- (etc...)
>
> 'a' is an lvalue expression compatible with the effective type of the
> object denoted by 'a'.
>

C doesn't seem to have this wording. But C++ says:

The object representation of an object of type T is the sequence of N
unsigned char objects taken up by the object of type T, where N equals
sizeof(T).

However I may well be wrong here, and this is only a conceptual description,
and not really a description of actual unsigned char objects covering the
same storage. In that case, the below array case and the struct case show
other situations making my point.

>> The reason is, that each object contains a sequence of `sizeof(obj)`
>> `unsigned char` objects (called the "object representation"). If we
>> change the `int`, we will change some or all of those objects. However,
>> the aliasing rule only states we are allowed to change a `int` by an
>> `char` or `unsigned char`, but not the other way around. Another example
>>
>> int a[1];
>> int *ra = a;
>> *ra = 0;
>>
>> Only one direction is described by 3.10/15 ("An aggregate or union type
>> that includes..."), but this time we need the other way around ("A type
>> that is the element or non-static data member type of an aggregate...").
>
> I may be missing your point, but this case seems to me perfectly well
> covered by the 6.5 p7 in C and 3.10 p15 in C++. The effective type of
> *ra is int and that is compatible (actually the same as) the effective
> type of the object being accessed. In C++ the access is through an
> lvalue expression with the dynamic type of the object (both are int).
>

The above changes two objects. The first has type int, and the second has
type int[1]. The change of a int[1] by an lvalue of type int is not covered
anywhere at 3.10 p15 in C++. It's not covered by the C rules either, it
seems.

Same situation with structs:

struct A { int a; };
A a;
int *p = &a.a;
*p = 0;

This changes the stored value of an A object by an lvalue of type int. Where
is this covered?

Is my understanding of this wrong?

Ben Bacarisse

unread,
Jan 21, 2011, 1:44:02 PM1/21/11
to
"Johannes Schaub (litb)" <schaub-...@web.de> writes:

> Ben Bacarisse wrote:
>
>> "Johannes Schaub (litb)" <schaub-...@web.de> writes:
>>
>>> Would we be allowed to do this in the opposite direction, if we know that
>>> the alignment is fine?
>>
>> What's the opposite direction? Are you asking if changing the int will
>> change the value of *b? If so, yes (provided the new int value's bits
>> do indeed affect the byte in question).
>>
>
> I mean to ask: If aliasing of an A object by an lvalue of type B is OK, is
> aliasing of a B object by an lvalue of type A OK?

No, not as far as I can see. Let's assume a system with sizeof(int) ==
1 and where int requires no more alignment than char. On such a system
this

int a;
char *cp = (void *)&a;
*cp = 42;

is fine by C's rules (and the intent is probably that C++ be the same in
this regard) but this

char a;
int *ip = (void *)&a;
*ip = 42;

is undefined. It is explicitly undefined by 6.5 p7 whereas the first is
explicitly permitted by that text.

>> If you mean can we change an array of char (suitable aligned) by writing
>> though and int *, then the answer is no.
>>
>>> The issue I'm seeing is that the aliasing rule does not cover the simple
>>> case of the following, if one considers the aliasing rule as a
>>> non-symmetric relation
>>>
>>> int a;
>>> a = 0;
>>
>> The wording in the C standard covers that case explicitly:
>>
>> An object shall have its stored value accessed only by an lvalue
>> expression that has one of the following types:
>>
>> -- a type compatible with the effective type of the object,
>> -- (etc...)
>>
>> 'a' is an lvalue expression compatible with the effective type of the
>> object denoted by 'a'.
>>
>
> C doesn't seem to have this wording.

What wording? The indented text is from section 6.5 paragraph 7 in
n1256.pdf (I don't have the "real thing").

> But C++ says:
>
> The object representation of an object of type T is the sequence of N
> unsigned char objects taken up by the object of type T, where N equals
> sizeof(T).

There is similar wording for C in 6.2.6.1 p4. It is not exactly the
same but I think it has pretty much the same effect. However, I am not
sure how this wording relates to your question.

> However I may well be wrong here, and this is only a conceptual description,
> and not really a description of actual unsigned char objects covering the
> same storage. In that case, the below array case and the struct case show
> other situations making my point.
>
>>> The reason is, that each object contains a sequence of `sizeof(obj)`
>>> `unsigned char` objects (called the "object representation"). If we
>>> change the `int`, we will change some or all of those objects. However,
>>> the aliasing rule only states we are allowed to change a `int` by an
>>> `char` or `unsigned char`, but not the other way around. Another example
>>>
>>> int a[1];
>>> int *ra = a;
>>> *ra = 0;
>>>
>>> Only one direction is described by 3.10/15 ("An aggregate or union type
>>> that includes..."), but this time we need the other way around ("A type
>>> that is the element or non-static data member type of an aggregate...").
>>
>> I may be missing your point, but this case seems to me perfectly well
>> covered by the 6.5 p7 in C and 3.10 p15 in C++. The effective type of
>> *ra is int and that is compatible (actually the same as) the effective
>> type of the object being accessed. In C++ the access is through an
>> lvalue expression with the dynamic type of the object (both are int).
>>
>
> The above changes two objects. The first has type int, and the second has
> type int[1]. The change of a int[1] by an lvalue of type int is not covered
> anywhere at 3.10 p15 in C++. It's not covered by the C rules either, it
> seems.

I see your point but I don't think that's what is intended. The object
being accessed is an int, not the array that contains it. The fact that
the access changes a larger containing object does not seem to alter the
validity of the access in question. Note the wording (in C) is all
about the access, not about what that access changes.

> Same situation with structs:
>
> struct A { int a; };
> A a;
> int *p = &a.a;
> *p = 0;
>
> This changes the stored value of an A object by an lvalue of type int. Where
> is this covered?
>
> Is my understanding of this wrong?

I think it is covered in C and in C++ because what is being accessed is
'a.a' not 'a'. The fact that a changes as a result is not ruled out by
the wording that permits the access of the sub-object.

--
Ben.

Dag-Erling Smørgrav

unread,
Jan 21, 2011, 3:35:54 PM1/21/11
to
Ben Bacarisse <ben.u...@bsb.me.uk> writes:
> "Johannes Schaub (litb)" <schaub-...@web.de> writes:
> > I mean to ask: If aliasing of an A object by an lvalue of type B is
> > OK, is aliasing of a B object by an lvalue of type A OK?
> No, not as far as I can see.

The correct answer is "it depends". The OP used an example where B is
char and A is something larger, which is a special case that works in
one direction but not the other, but if e.g. A and B are struct types
with a common prefix, the answer is yes. A good real-world example is
the various flavors of struct sockaddr in the BSD socket API.

DES
--
Dag-Erling Smørgrav - d...@des.no

Marcin Grzegorczyk

unread,
Jan 21, 2011, 4:45:06 PM1/21/11
to
Johannes Schaub (litb) wrote:
> Hello all. I had a discussion with someone on IRC and this question turned
> up. We are allowed by the Standard to change an object of type `int` by a
> `char` lvalue.
>
> int a;
> char *b = (char*)&a;
> *b = 0;
>
> Would we be allowed to do this in the opposite direction, if we know that
> the alignment is fine?

Apparently not, as far as C is concerned. By 6.5p6 [all chapter and
verse references to N1256 unless indicated otherwise], "The effective
type of an object for an access to its stored value is the declared type
of the object, if any".

One consequence of the effective type rules is that the common technique
of providing a custom memory allocation scheme for freestanding
implementations, using a static array of unsigned char as the memory
pool, leads to undefined behaviour when you try to use memory allocated
on that pool as, say an array of ints -- even if you have a way to
specify the alignment (which C1X will provide). Indeed, the only
standard C way to get storage without declared type is malloc() and
friends, which of course need not be available in a freestanding
implementation.

[...]


> Another example
>
> int a[1];
> int *ra = a;
> *ra = 0;
>
> Only one direction is described by 3.10/15 ("An aggregate or union type that
> includes..."), but this time we need the other way around ("A type that is
> the element or non-static data member type of an aggregate...").
>
> Is the other direction implied? The above spec quotes are from C++, but I
> beleive same things apply to C.

Yes, C99 has the same wording (6.5p7). Incidentally, the ISO C
Committee has already acknowledged that this exact clause is confusing,
and even tried - twice - to improve the effective type rules (see WG14
documents N1409 and N1520); in both cases, the decision was "more work
needed".
--
Marcin Grzegorczyk

Ben Bacarisse

unread,
Jan 21, 2011, 5:30:11 PM1/21/11
to
Dag-Erling Smørgrav <d...@des.no> writes:

Maybe there is C/C++ difference here but in C accessing a struct A as
if it were a struct B is undefined[1]. The old BSD socket API is going
to work because the implementation can't use the permission that the
aliasing rule give it in any way that would defeat the code. However,
in code such as this:

struct s1 { int i; double d; };
struct s2 { int i; float f; };

int f(struct s1 *s1p, struct s2 *s2p)
{
int x = s1p->i;
s2p->i = 42;
return x * s1p->i;
}

an implementation is permitted to assume that s1p->i has not changed and
to re-use the value of x in the return.

In some sense you are correct to say that it depends because there are
examples where Johannes Schaub's rule is OK (for example when A is char
and B is unsigned char) but I took the question to be a general one: is
it always OK to reverse the types? That's why I said "no" and gave a
single counter-example.

[1] There is a special clause in C about structs that share an initial
segment when both are members of the same union, but that is not what
you are talking about.

--
Ben.

lawrenc...@siemens.com

unread,
Jan 21, 2011, 5:23:46 PM1/21/11
to
In comp.std.c "Johannes Schaub (litb)" <schaub-...@web.de> wrote:
>
> The above changes two objects. The first has type int, and the second has
> type int[1]. The change of a int[1] by an lvalue of type int is not covered
> anywhere at 3.10 p15 in C++. It's not covered by the C rules either, it
> seems.
>
> Same situation with structs:
>
> struct A { int a; };
> A a;
> int *p = &a.a;
> *p = 0;
>
> This changes the stored value of an A object by an lvalue of type int. Where
> is this covered?

I can't speak for C++, but those cases are both covered in C by the next
to last rule in 6.5p7 ("an aggregate or union type that includes one of
the aforementioned types...").
--
Larry Jones

Everything's gotta have rules, rules, rules! -- Calvin

Joshua Maurice

unread,
Jan 21, 2011, 6:36:16 PM1/21/11
to
On Jan 21, 9:20 am, "Johannes Schaub (litb)" <schaub-johan...@web.de>
wrote:
> Ben Bacarisse wrote:

> > "Johannes Schaub (litb)" <schaub-johan...@web.de> writes:
>
> >> Would we be allowed to do this in the opposite direction, if we know that
> >> the alignment is fine?
>
> > What's the opposite direction?  Are you asking if changing the int will
> > change the value of *b?  If so, yes (provided the new int value's bits
> > do indeed affect the byte in question).
>
> I mean to ask: If aliasing of an A object by an lvalue of type B is OK, is
> aliasing of a B object by an lvalue of type A OK?

No. Don't think about it as an aliasing rule. Think about it as a rule
which restricts the types of lvalues with which you can legally access
objects.

You can always access an object through a char or unsigned char
lvalue. (Or maybe it's only for POD types - there's no consensus. I
would only use char and unsigned char to access POD objects.)

You can always access an object through a base class lvalue, but you
can never do the reverse: you can never take a complete object of type
T and access it through a derived type of type T.

Joshua Maurice

unread,
Jan 21, 2011, 6:42:07 PM1/21/11
to
On Jan 21, 2:23 pm, lawrence.jo...@siemens.com wrote:

> In comp.std.c "Johannes Schaub (litb)" <schaub-johan...@web.de> wrote:
>
>
>
> > The above changes two objects. The first has type int, and the second has
> > type int[1]. The change of a int[1] by an lvalue of type int is not covered
> > anywhere at 3.10 p15 in C++. It's not covered by the C rules either, it
> > seems.
>
> > Same situation with structs:
>
> >     struct A { int a; };
> >     A a;
> >     int *p = &a.a;
> >     *p = 0;
>
> > This changes the stored value of an A object by an lvalue of type int. Where
> > is this covered?
>
> I can't speak for C++, but those cases are both covered in C by the next
> to last rule in 6.5p7 ("an aggregate or union type that includes one of
> the aforementioned types...").

I can't speak to the exact standardeze offhand, but accessing an
object through an appropriately created lvalue referring to a sub-
object is perfectly sensible behavior. "*p" is an appropriately
obtained lvalue which refers to a sub-object of the complete object,
and it's perfectly fine to access the complete object through that
lvalue to sub-object.

By appropriately obtained, I mean that it actually "points to" the sub-
object. Ex:

struct Foo { int a; int b; };
int main()
{
Foo f;
int* x = reinterpret_cast<int*>(&f) + 1;
*x = 1;
return f.b;
}

The above is definitely not portable, and has undefined behavior on at
least some platforms. There's no guarantee that "*x" actually refers
to the sub-object "f.b". You need to obtain it through "appropriate"
means, such as:

struct Foo { int a; int b; };
int main()
{
Foo f;
int* x = & f.b;
*x = 1;
return f.b;
}

Johannes Schaub (litb)

unread,
Jan 21, 2011, 6:48:21 PM1/21/11
to
lawrenc...@siemens.com wrote:

Well, that's the exact other way around. I'm using an int lvalue to access
the value of an object whose effective type is A. But that rule only grants
me to use an lvalue of type A to change the stored value of the member
object whose effective type is int.

Am I missing something?

James Kuyper

unread,
Jan 22, 2011, 6:36:45 AM1/22/11
to
On 01/21/2011 05:30 PM, Ben Bacarisse wrote:
> Dag-Erling Smørgrav<d...@des.no> writes:
>
>> Ben Bacarisse<ben.u...@bsb.me.uk> writes:
>>> "Johannes Schaub (litb)"<schaub-...@web.de> writes:
>>>> I mean to ask: If aliasing of an A object by an lvalue of type B is
>>>> OK, is aliasing of a B object by an lvalue of type A OK?
>>> No, not as far as I can see.
>>
>> The correct answer is "it depends". The OP used an example where B is
>> char and A is something larger, which is a special case that works in
>> one direction but not the other, but if e.g. A and B are struct types
>> with a common prefix, the answer is yes. A good real-world example is
>> the various flavors of struct sockaddr in the BSD socket API.
>
> Maybe there is C/C++ difference here but in C accessing a struct A as
> if it were a struct B is undefined[1].
...

> [1] There is a special clause in C about structs that share an initial
> segment when both are members of the same union, but that is not what
> you are talking about.

I think that this is precisely what he was was referring to as a "common
prefix".

--
James Kuyper

James Kuyper

unread,
Jan 22, 2011, 6:58:15 AM1/22/11
to

No - the reverse is not allowed: trying to access an object of type int
using an lvalue of type struct A has undefined behavior. Consider that
struct A might be padded to an alignment larger than sizeof(int), and
consider that many of the operations permitted on an lvalue of struct
type can access bytes that are part of that struct, but not part of the
'a' member.

It's unlikely to be true in this case, but it would be more likely if
'a' were a smaller non-character type, such as a 16-bit short, on a
machine with a large word size, say 64-bits. A compiler for such a
machine might pad a struct 'A' to 64 bits. Then, when writing the value
of the a member of a struct 'A', it might write the entire 64 bits,
confident that the extra 48 bits would end up in memory reserved for the
struct. That will cause problems if your lvalue of type struct A
actually refers to memory containing a short that was an element in an
array of shorts. In that case, those other 48 bits would contain other
elements of that array.

You can prove the existence of a similar aliasing problem involving
arrays and their elements, by simply exchanging the roles of the array
type and the struct type in the above argument.

--
James Kuyper

James Kuyper

unread,
Jan 22, 2011, 7:08:54 AM1/22/11
to
On 01/21/2011 06:36 PM, Joshua Maurice wrote:
...

> No. Don't think about it as an aliasing rule. Think about it as a rule
> which restricts the types of lvalues with which you can legally access
> objects.

What distinction is there between an aliasing rule and that description?
That description seems, to me, to be a fairly good definition of what
"aliasing rule" means, at least in the context of C or C++.

> You can always access an object through a char or unsigned char
> lvalue. (Or maybe it's only for POD types - there's no consensus. I
> would only use char and unsigned char to access POD objects.)

I don't have a copy of the current C++ standard, nor of the latest draft
of the next standard - the closest that I have is n3035.pdf. In 3.10p15,
it makes the same exception for access through an lvalue of char and
unsigned char type that C does, and that exception is not tied to the
POD-ness of the dynamic type.

--
James Kuyper

Johannes Schaub (litb)

unread,
Jan 22, 2011, 8:01:07 AM1/22/11
to
James Kuyper wrote:

That's not true from an aliasing point of view. 3.10/15 explicitly allows
this:

an aggregate or union type that includes one of the aforementioned

types among its elements or non-static data members

> Consider that
> struct A might be padded to an alignment larger than sizeof(int), and
> consider that many of the operations permitted on an lvalue of struct
> type can access bytes that are part of that struct, but not part of the
> 'a' member.
>

We would then violate alignment requirements. This is a different thing from
aliasing requirement though.

Seebs

unread,
Jan 22, 2011, 2:36:12 PM1/22/11
to
On 2011-01-22, James Kuyper <james...@verizon.net> wrote:
> On 01/21/2011 06:36 PM, Joshua Maurice wrote:
> ...
>> No. Don't think about it as an aliasing rule. Think about it as a rule
>> which restricts the types of lvalues with which you can legally access
>> objects.

> What distinction is there between an aliasing rule and that description?
> That description seems, to me, to be a fairly good definition of what
> "aliasing rule" means, at least in the context of C or C++.

return foo(int *i1, int *i2) {
*i1 = 1;
*i2 = 2;
return *i1;
}

The reason this might return either 1 or 2 is aliasing, but has nothing
to do with the types with which you can legally access objects.

-s
--
Copyright 2010, all wrongs reversed. Peter Seebach / usenet...@seebs.net
http://www.seebs.net/log/ <-- lawsuits, religion, and funny pictures
http://en.wikipedia.org/wiki/Fair_Game_(Scientology) <-- get educated!
I am not speaking for my employer, although they do rent some of my opinions.

Joshua Maurice

unread,
Jan 22, 2011, 5:29:34 PM1/22/11
to
On Jan 22, 11:36 am, Seebs <usenet-nos...@seebs.net> wrote:

> On 2011-01-22, James Kuyper <jameskuy...@verizon.net> wrote:
>
> > On 01/21/2011 06:36 PM, Joshua Maurice wrote:
> > ...
> >> No. Don't think about it as an aliasing rule. Think about it as a rule
> >> which restricts the types of lvalues with which you can legally access
> >> objects.
> > What distinction is there between an aliasing rule and that description?
> > That description seems, to me, to be a fairly good definition of what
> > "aliasing rule" means, at least in the context of C or C++.
>
>         return foo(int *i1, int *i2) {
>                 *i1 = 1;
>                 *i2 = 2;
>                 return *i1;
>         }
>
> The reason this might return either 1 or 2 is aliasing, but has nothing
> to do with the types with which you can legally access objects.
>
> -s
> --
> Copyright 2010, all wrongs reversed.  Peter Seebach / usenet-nos...@seebs.nethttp://www.seebs.net/log/<-- lawsuits, religion, and funny pictureshttp://en.wikipedia.org/wiki/Fair_Game_(Scientology) <-- get educated!

> I am not speaking for my employer, although they do rent some of my opinions.

Apparently it was cross-posted, and I didn't notice, and someone
replied without the cross posting, and I definitely didn't notice.

Seebs is right that
int foo(int *i1, int *i2) {


*i1 = 1;
*i2 = 2;
return *i1;
}

If i1 and i2 alias, then it returns 2. If they don't alias, then it
returns 1. This function doesn't have a violation of the "strict
aliasing rules", which would be better called "effective type access
rules".

Let's consider this function though:
int foo(int* x, short* y)
{
*x = 1;
*y = 2;
return 1;
}
int bar(int* x, short* y)
{
*x = 1;
*y = 2;
return *x;
}
Let's consider functions foo and bar. Let's suppose that x and y alias
in both. For function foo, there is no undefined behavior even though
both alias (at least according to what appears to be the prominent
interpretation of these rules). For function bar, if they alias, then
we have undefined behavior. Both have aliasing of short* and int*, but
only one has undefined behavior. It has undefined behavior because
there is a read of a short object through an int lvalue in function
bar.

Obviously aliasing is a required component of this analysis, but the
undefined behavior results from using an lvlaue to access an object.
You can use a char lvalue to access an int object, but you cannot use
an int lvalue to access a char object (or char array).

Ben Bacarisse

unread,
Jan 22, 2011, 9:19:29 PM1/22/11
to
James Kuyper <james...@verizon.net> writes:

I don't see how it can be. For one thing, the various flavors of
struct sockaddr are not in a union object (at least they were not used
in that way the last time I used the BSD API).

But more importantly, the special exception is not an exception to the
effective type access rules. It is always possible (from the point of
view of these rules) to access a member of a union that is not the one
last stored -- the bits are simply reinterpreted (possibly as a value of
a different type. That is as true for structures that don't share a
common prefix as it is for ones that do (and it's true for non-structure
types as well). What 6.5.2.3 p5 does is ensure that there won't be any
surprises about what data you actually get when there is a common prefix.

If the exception given in 6.5.2.3 p5 were not there, the access itself
to these common prefix elements would not suddenly become undefined --
the only difference would be that you would not be able to rely on
getting the data you expect.

--
Ben.

James Kanze

unread,
Jan 24, 2011, 5:29:33 AM1/24/11
to

The C++ standard makes the exception allowing access through
a char or unsigned char type in the case where "a program
attempts access the stored value of an object". I'm not sure of
the exact intent, but accessing the stored value means a read
access. The standard doesn't seem to say anything about
modifying, except for the case where the access is to
a nonmodifiable lvalue. I would hope that the intent would be
more or less:

float f = 3.14159;
unsigned char* pc = reinterpret_cast<unsigned char*>(&f);
printf("%u\n", *pc); // Legal.
*pc = 0xFF; // Legal? Or UB?
std::cout << f << std::endl; // UB (maybe a trapping NaN)

There is (or should be) a clear exception, however, for memcpy
like operations: the following must be legal:

float f = 3.14159;
unsigned char buff[sizeof(float)];
memcpy(buff, &f, sizeof(float));
float other;
memcpy(&other, buff, sizeof(float));
printf("%.5f\n", other); // Must output 3.14159

This is in §3.9/3 in the C++ standard, and only for PODs in
C++. In this case, I'm fairly certain that the intent is for
C and C++ to be compatible in this regard, at least for PODs.

At any rate, the above more or less corresponds to what actually
happens on existing hardware. (To have similar problems with
int, you need some fairly exotic hardware; I think a Univac MPS
might fill the bill.)

--
James Kanze

James Kanze

unread,
Jan 24, 2011, 5:49:06 AM1/24/11
to
On Jan 22, 10:29 pm, Joshua Maurice <joshuamaur...@gmail.com> wrote:
> On Jan 22, 11:36 am, Seebs <usenet-nos...@seebs.net> wrote:
> > On 2011-01-22, James Kuyper <jameskuy...@verizon.net> wrote:

> > > On 01/21/2011 06:36 PM, Joshua Maurice wrote:
> > > ...
> > >> No. Don't think about it as an aliasing rule. Think about it as a rule
> > >> which restricts the types of lvalues with which you can legally access
> > >> objects.
> > > What distinction is there between an aliasing rule and that description?
> > > That description seems, to me, to be a fairly good definition of what
> > > "aliasing rule" means, at least in the context of C or C++.

> > return foo(int *i1, int *i2) {
> > *i1 = 1;
> > *i2 = 2;
> > return *i1;
> > }

> > The reason this might return either 1 or 2 is aliasing, but has nothing
> > to do with the types with which you can legally access objects.

> Apparently it was cross-posted, and I didn't notice, and someone


> replied without the cross posting, and I definitely didn't notice.

> Seebs is right that
> int foo(int *i1, int *i2) {
> *i1 = 1;
> *i2 = 2;
> return *i1;
> }
> If i1 and i2 alias, then it returns 2. If they don't alias, then it
> returns 1. This function doesn't have a violation of the "strict
> aliasing rules", which would be better called "effective type access
> rules".
>
> Let's consider this function though:
> int foo(int* x, short* y)
> {
> *x = 1;
> *y = 2;
> return 1;
> }
> int bar(int* x, short* y)
> {
> *x = 1;
> *y = 2;
> return *x;
> }

> Let's consider functions foo and bar. Let's suppose that x and
> y alias in both. For function foo, there is no undefined
> behavior even though both alias (at least according to what
> appears to be the prominent interpretation of these rules).

I'm not sure about C here, but in C++, there is definitely
undefined behavior in foo if x and y alias. In fact, there
would be undefined behavior even if foo were simply:

void foo(const int* x, const short* y)
{
printf("%d, %d\n", *x, *y)
}

If the two pointers point to the same physical address, there is
no way that the memory they point to can be both an int and
a short. And the C++ standard clearly says:
If a program attempts to access the stored value of an
object through an lvalue of other than one of the following
types the behavior is undefined:
[...]
and short for int or vice versa isn't in the list. And I'm
certain that the intent in C is the same: C definitly allows
trapping representations for integer values, and reading part of
an int as a short could conceivably result in a trapping
representation for a short. (Think of a one's complement
machine which traps on -0.)

The problem becomes more interesting if we replace short with
unsigned char. In that case, my version is legal and defined
behavior: accessing a stored value through an lvalue of char or
unsgiend char type is in the list after the cited paragraph.
(IIRC, in C, this exception only applies to unsigned char; for
some reason, C++ added plain char to the list.) But what about
the original version, which modifies. Is modifying an int
through an unsigned char* undefined behavior? What if it
results in a trapping representation in the int? Or is it just
undefined behavior if you access the int? And of course,
modifying all of the bytes in the int, from a bytewise copy of
another int, has to be fully defined behavior.

--
James Kanze

Joshua Maurice

unread,
Jan 24, 2011, 6:44:12 PM1/24/11
to

Is the following a well-formed C++ program without UB?

#include <cstdlib>
using namespace std;
int main()
{
void* p = malloc(sizeof(int) + sizeof(float));
int*x = (int*) p;
*x = 1;
}

What about the following?

#include <cstdlib>
using namespace std;
int main()
{
void* p = malloc(sizeof(int) + sizeof(float));
int*x = (int*) p;
*x = 1;
float* y = (float*) p;
*y = 1;
}

I've had a thread up on comp.std.c++ for a while now about these
issues, and I've gotten 0 replies. It's quite frustrating.

In short, I would argue that both of the above programs have no UB in C
++, nor their equivalent program in C. You need both programs above to
have no UB in order to have user-space memory allocators in standard
conforming C++. I think that the standard's intent is not to forbid
user-space C++ standard conforming pooling memory allocators.

Let's look at "3.8 Objectlifetime / 1, 2, 4, 5, 6, and 7". Each of
those sections make reference to "reusing storage", something which is
distinct from "releasing the storage". "Reusing the storage" of an
object ends that object's lifetime. What else can this mean besides
the following?

void* p = malloc(sizeof(int) + sizeof(float));
int*x = (int*) p;
*x = 1;
float* y = (float*) p;
*y = 1; /* reuse of storage, the int object's lifetime ends, and
the float object's lifetime begins */

Furthermore, let's look at the rules in "3.8 Object Lifetime". "3.8
Object Lifetime / 1" is actually nonsensical as written. Consider:
void* p = malloc(sizeof(char))
Well, we've allocated storage with proper alignment and type for an
arbitrarily large number of types, and if those types have a trivial
constructor, such as:
struct T1 {};
struct T2 {};
struct T3 {};
//etc.
then an object of each of those types exists at that location. So, an
arbitrarily large number of distinct complete objects coexist in "*p"
according to that reading of the rules, which is entirely
nonsensical.

Unfortunately, as I've expounded at length in the thread on comp.std.c+
+, the sensible way forward isn't clear. However, some of the proposed
changes to C++0x in 3.10 / 15 are taking the language in quite the
wrong direction IMO.

We need to solve a couple of basic problems. The most important and
basic is: when does the lifetime of a POD class even begin? Consider:

#include <cstdlib>
using namespace std;

struct T1 { int x; int y; };
struct T2 { int x; int y; };

int main()
{
void* p = 0;
T1 * t1 = 0;
T2 * t2 = 0;
int * x = 0;

if (sizeof(T1) != sizeof(T2))
return 1;
if ( (char*)(& t1->y) - (char*) (& t1) != (char*)(& t2->y) -
(char*) (& t2) )
return 1;

p = malloc(sizeof(T1));
/* Do we have a T1 object here? Presumably no. Otherwise we also
have a T2 object here, and we definitely don't want to start talking
about two distinct complete objects occupying the same storage at the
same time. */

t1 = (T1*) p;
/* T1 object yet? Presumably the answer hasn't changed since the
above comment. */

x = & t1->x;
/* T1 object yet? */

*x = 1;
/* Do we have a T1 object here? Maybe. I just see a write through
an int lvalue. I see no writes nor reads through a T1 lvalue. I see
nothing that favors T1 over T2, besides some sort of data dependency
analysis through the member-of operator. However, there isn't even a
hint of data dependency analysis in the standard with regards to
object lifetime rules. */

x = & t1->y;
*x = 2;
/* Do we have a T1 object here? The answer must be yes, or we'll
never have a T1 object. However, again, I see nothing to favor having
a T1 object over a T2 object besides data dependency analysis through
the member-of operator. */

t2 = (T2*) p;
return t2->y; /* UB? Why? Why is reading "t1->y" not UB, but
reading "t2->y" is UB? In other words, why do we have a T1 object, but
not a T2 object? */
}

Also, what if we used offsetof hackery to initialize both int members
of the T1 object without using a member-of operator on a T1 lvalue?

As far as I can tell, gcc doesn't even bother doing aliasing analysis
on anything besides primitive types, for exactly the reasons outlined
above. They must not have seen a sensible way to differentiate between
T1 and T2, just as I cannot.

Joshua Maurice

unread,
Jan 24, 2011, 6:47:17 PM1/24/11
to
On Jan 24, 3:44 pm, Joshua Maurice <joshuamaur...@gmail.com> wrote:
> On Jan 24, 2:49 am, James Kanze <james.ka...@gmail.com> wrote:
>     if ( (char*)(& t1->y) - (char*) (& t1) != (char*)(& t2->y) -
> (char*) (& t2) )
>       return 1;

Err, let me fix that. That should be:

if ( (char*)(& t1->y) - (char*) (t1) != (char*)(& t2->y) -
(char*) (t2) )
return 1;

The goal is to test if they have the same layout, and only run the
rest of the program if they do.

Ben Bacarisse

unread,
Jan 24, 2011, 10:15:42 PM1/24/11
to

This is cross posted and I don't think the C side of the questions have
been properly answered. My answer is about C only.

Joshua Maurice <joshua...@gmail.com> writes:

> On Jan 24, 2:49 am, James Kanze <james.ka...@gmail.com> wrote:
>> On Jan 22, 10:29 pm, Joshua Maurice <joshuamaur...@gmail.com> wrote:
>> > Let's consider this function though:
>> >   int foo(int* x, short* y)
>> >   {
>> >     *x = 1;
>> >     *y = 2;
>> >     return 1;
>> >   }
>> >   int bar(int* x, short* y)
>> >   {
>> >     *x = 1;
>> >     *y = 2;
>> >     return *x;
>> >   }
>> > Let's consider functions foo and bar. Let's suppose that x and
>> > y alias in both. For function foo, there is no undefined
>> > behavior even though both alias (at least according to what
>> > appears to be the prominent interpretation of these rules).
>>
>> I'm not sure about C here, but in C++, there is definitely
>> undefined behavior in foo if x and y alias.

If x and y both point to the same allocated object, then neither
function is undefined. The assignments set the "effective type" of the
allocated object.

>>  In fact, there
>> would be undefined behavior even if foo were simply:
>>
>>     void foo(const int* x, const short* y)
>>     {
>>         printf("%d, %d\n", *x, *y)
>>     }

Again, this is not always UB when the object being aliased is allocated
rather than declared. When the aliased object is allocated, whether the
accesses are defined or not depends on the effective type of the aliased
allocated object. To be certain of UB when the pointers point to the
same allocated object you need something like this:

void foo(int *x, short *y)
{
*y = 1;
printf("%d\n", *x);
}

The assignment ensures that the effective type of the allocated object
is int so the the second is undefined.

>> If the two pointers point to the same physical address, there is
>> no way that the memory they point to can be both an int and
>> a short.

In C it can be if the storage is allocated and only stores are done (as
in the first foo and bar above).

>>  And the C++ standard clearly says:
>>     If a program attempts to access the stored value of an
>>     object through an lvalue of other than one of the following
>>     types the behavior is undefined:
>>     [...]
>> and short for int or vice versa isn't in the list.  And I'm
>> certain that the intent in C is the same: C definitly allows
>> trapping representations for integer values, and reading part of
>> an int as a short could conceivably result in a trapping
>> representation for a short.  (Think of a one's complement
>> machine which traps on -0.)
>>
>> The problem becomes more interesting if we replace short with
>> unsigned char.  In that case, my version is legal and defined
>> behavior: accessing a stored value through an lvalue of char or
>> unsgiend char type is in the list after the cited paragraph.
>> (IIRC, in C, this exception only applies to unsigned char; for
>> some reason, C++ added plain char to the list.)

In C, the wording is "a character type" which covers char and both
signed and unsigned char. As you say, it is odd (at last at first
glance -- I am not a C++ expert) that C++ added char but not signed
char to the list.

>>  But what about
>> the original version, which modifies.  Is modifying an int
>> through an unsigned char* undefined behavior?  What if it
>> results in a trapping representation in the int?  Or is it just
>> undefined behavior if you access the int?  And of course,
>> modifying all of the bytes in the int, from a bytewise copy of
>> another int, has to be fully defined behavior.
>
> Is the following a well-formed C++ program without UB?
>
> #include <cstdlib>
> using namespace std;
> int main()
> {
> void* p = malloc(sizeof(int) + sizeof(float));
> int*x = (int*) p;
> *x = 1;
> }

You ask below about the C equivalents of these. This one would defined
in C given the obvious alterations required.

> What about the following?
>
> #include <cstdlib>
> using namespace std;
> int main()
> {
> void* p = malloc(sizeof(int) + sizeof(float));
> int*x = (int*) p;
> *x = 1;
> float* y = (float*) p;
> *y = 1;
> }

Again, in C, this is well-defined due the definition of effective type.

> I've had a thread up on comp.std.c++ for a while now about these
> issues, and I've gotten 0 replies. It's quite frustrating.
>
> In short, I would argue that both of the above programs have no UB in C
> ++, nor their equivalent program in C.

<snip much more C++ specific questions>
--
Ben.

Joshua Maurice

unread,
Jan 24, 2011, 10:33:29 PM1/24/11
to
On Jan 24, 7:15 pm, Ben Bacarisse <ben.use...@bsb.me.uk> wrote:
> > What about the following?
>
> >   #include <cstdlib>
> >   using namespace std;
> >   int main()
> >   {
> >     void* p = malloc(sizeof(int) + sizeof(float));
> >     int*x = (int*) p;
> >     *x = 1;
> >     float* y = (float*) p;
> >     *y = 1;
> >   }
>
> Again, in C, this is well-defined due the definition of effective type.
>
> > I've had a thread up on comp.std.c++ for a while now about these
> > issues, and I've gotten 0 replies. It's quite frustrating.
>
> > In short, I would argue that both of the above programs have no UB in C
> > ++, nor their equivalent program in C.
>
> <snip much more C++ specific questions>

I'd like to think that the rest of the questions applied to C as well
as C++. Surely simple things like:
#include <stdlib.h>


int main()
{
void* p = malloc(sizeof(int) + sizeof(float));

* ( (float*) p ) = 1;
* ( (int*) p ) = 1;
return * ( (int*) p );
}
either work in C and C++, or work in neither. The above is a well
formed C program and a well formed C++ program. I would be slightly
surprised if it had UB in one and not UB in the other.

I would also like to hear some of the actual people on the committees
weigh in on these particular questions. I've been asking questions
like these for a while now, and I have yet to hear compelling answers
from the people on the committees or those who would know, and/or the
actual compiler writers.

Ben Bacarisse

unread,
Jan 24, 2011, 11:10:17 PM1/24/11
to
Joshua Maurice <joshua...@gmail.com> writes:

> On Jan 24, 7:15 pm, Ben Bacarisse <ben.use...@bsb.me.uk> wrote:
>> > What about the following?
>>
>> >   #include <cstdlib>
>> >   using namespace std;
>> >   int main()
>> >   {
>> >     void* p = malloc(sizeof(int) + sizeof(float));
>> >     int*x = (int*) p;
>> >     *x = 1;
>> >     float* y = (float*) p;
>> >     *y = 1;
>> >   }
>>
>> Again, in C, this is well-defined due the definition of effective type.
>>
>> > I've had a thread up on comp.std.c++ for a while now about these
>> > issues, and I've gotten 0 replies. It's quite frustrating.
>>
>> > In short, I would argue that both of the above programs have no UB in C
>> > ++, nor their equivalent program in C.
>>
>> <snip much more C++ specific questions>
>
> I'd like to think that the rest of the questions applied to C as well
> as C++.

At that point you quoted some passages from the C++ standard and started
using phrases like "reused" which seems to be key to the C++ behaviour
but does not crop up in C. I worried that C-specific answers beyond
that point might just confuse matters. I got the feeling your real
worries were about whether C++ defined the code you were posting about.

> Surely simple things like:
> #include <stdlib.h>
> int main()
> {
> void* p = malloc(sizeof(int) + sizeof(float));
> * ( (float*) p ) = 1;
> * ( (int*) p ) = 1;
> return * ( (int*) p );
> }

That's fine in C. It is not really different from the example I did
comment on -- switching to a cast expression from an initialised
variable does not alter the meaning.

> either work in C and C++, or work in neither. The above is a well
> formed C program and a well formed C++ program. I would be slightly
> surprised if it had UB in one and not UB in the other.

So would I, but the C++ standard uses different language from the C
standard about the validity of such accesses so a difference (even an
unintended one) is possible.

> I would also like to hear some of the actual people on the committees
> weigh in on these particular questions. I've been asking questions
> like these for a while now, and I have yet to hear compelling answers
> from the people on the committees or those who would know, and/or the
> actual compiler writers.

Have you got some reason to suspect that there is a problem with any of
these programs in C? The C standard seems quite clear on these specific
questions.

--
Ben.

Johannes Schaub (litb)

unread,
Jan 24, 2011, 11:33:46 PM1/24/11
to
Joshua Maurice wrote:

The C spec is clear that the above code is well-defined. The object "p"
points to has no declared type, so its effective type is the only measure of
type you have. The effective type by the float lvalue write is changed to
float for that write, and for all subsequent read-only accesses. The later
write by an int lvalue changes the effective type to an int for that write
and for all subsequent reads. Therefore, the last return is valid.

C++ doesn't have the concept of effective types, and the following is my
personal perception of the issue. Rather, in C++ the objects have type
themselfs, while in C types are merely an attribute of the access to
objects. So an object cannot exist without a type in C++; it wouldn't make
sense with the current model. The behavior of the above code is not clearly
defined in C++. Not even that other DR we talked about in comp.std.c++ fixes
it, because we are not really copying an object representation over in this
case.

Lots of people have different perception of the aliasing rules and of the
object model. In the GCC PR
http://gcc.gnu.org/bugzilla/show_bug.cgi?id=29286 they did not find
consensus on whether a plain write would start the lifetime of a new object
or not.

So what does it do? It probably does the same as in C. In the language
theory, the above is exactly the reason why unions work in C++. Writes to
different union members is exactly what starts and stops the lifetime of
respective member objects and what dictates the "active member" of the
union. Different people point to bullet 5 of 3.10/15, explaining that
lifetime stopping or starting would not be needed to make unions work, but
that's not quite correct. If you access an union member even through a class
member access expression, the final lvalue doesn't have the type of the
union. And even if you squint and assume the union lvalue is involved, you
have two lvalues that are involved, with the member lvalue causing an alias
conflict.

In the end here's another interesting difference in C and C++. In C and C++
the following have different meanings, if you go with the above explanation:

int a;
*(float*)&a = 0;

In C, this violates the aliasing rules and is undefined behavior, but in C++
it won't because as my assumption above states, the C++ model is solely
based on reads and writes to start and stop the lifetime of objects. In the
C++ case this is not necessarily undefined behavior. The size of float may
be larger than sizeof int or alignment requirements may be incompatible, in
which case it is undefined behavior. But sizeof(T) and alignment
requirements are implementation-defined, so this need not necessarily result
in undefined behavior *for a specific implementation* ("corresponding
instance" if the abstract machine). So the C++ standard requires the above
to work for certain implementations.

int a = 0;
float f = *(float*)&a;

This is undefined behavior in both C (same reason as above) and C++ (the
read won't stop the lifetime of the 'a' object of type int, and then violate
aliasing rules).

Personally I have given up aliasing and C++, because I feel I lack the
knowledge of platform details and compiler theory to make up a good
understanding. I will wait until this is fixed and then read a consistent
Standard, if that is ever going to happen.

Johannes Schaub (litb)

unread,
Jan 24, 2011, 11:37:00 PM1/24/11
to
(sorry i had a typo in the follow-up header).

Joshua Maurice wrote:

The C spec is clear that the above code is well-defined. The object "p"

Joshua Maurice

unread,
Jan 25, 2011, 12:11:29 AM1/25/11
to
On Jan 24, 8:10 pm, Ben Bacarisse <ben.use...@bsb.me.uk> wrote:

Yes. I've been getting various replies when I tweak the above program
just slightly.

#include <stdlib.h>
void foo(int* a, float* b)
{
*a = 1;
*b = 1;


}
int main()
{
void* p = malloc(sizeof(int) + sizeof(float));

foo((int*)p, (float*)p);
}

I asked if this had UB in comp.lang.c a while ago. I received various
replies, with little follow up discussion.

One reply was that a piece of memory may have at most one effective
type between calls to malloc and free.

Another reply was that this is a DR in the C and C++ language specs,
known colloquially as the union DR.

Another reply was that the above program has perfectly well defined
behavior, but the following has undefined behavior:
#include <stdlib.h>
int foo(int* a, float* b)
{
*a = 1;
*b = 1;
return *a;


}
int main()
{
void* p = malloc(sizeof(int) + sizeof(float));

foo((int*)p, (float*)p);
}
Specifically, this example explains how the compiler might use
aliasing analysis for optimization purposes. A conforming compiler may
not simply assume that an int* and a float* do not alias. However, if
analysis shows that aliasing would result in UB (as it would in the
above program when "return *a;" reads a float object through an int
lvalue) then the compiler is free to do whatever it wants in the face
of the UB, including assume that they don't alias.

I think I like the third option best, but my personal preferences
don't dictate what compilers actually do.

Joshua Maurice

unread,
Jan 25, 2011, 12:26:15 AM1/25/11
to
On Jan 24, 8:33 pm, "Johannes Schaub (litb)" <schaub-johan...@web.de>
wrote:

> Joshua Maurice wrote:
> > I'd like to think that the rest of the questions applied to C as well
> > as C++. Surely simple things like:
> >   #include <stdlib.h>
> >   int main()
> >   {
> >     void* p = malloc(sizeof(int) + sizeof(float));
> >     * ( (float*) p ) = 1;
> >     * ( (int*) p ) = 1;
> >     return * ( (int*) p );
> >   }
> > either work in C and C++, or work in neither. The above is a well
> > formed C program and a well formed C++ program. I would be slightly
> > surprised if it had UB in one and not UB in the other.
>
> The C spec is clear that the above code is well-defined. The object "p"
> points to has no declared type, so its effective type is the only measure of
> type you have. The effective type by the float lvalue write is changed to
> float for that write, and for all subsequent read-only accesses. The later
> write by an int lvalue changes the effective type to an int for that write
> and for all subsequent reads. Therefore, the last return is valid.

Sure. I get this. This makes sense. A write starts the lifetime of an
object if one didn't already exist, and ends the lifetime of any
object previously existing in that memory.

> C++ doesn't have the concept of effective types, and the following is my
> personal perception of the issue. Rather, in C++ the objects have type
> themselfs, while in C types are merely an attribute of the access to
> objects. So an object cannot exist without a type in C++; it wouldn't make
> sense with the current model. The behavior of the above code is not clearly
> defined in C++. Not even that other DR we talked about in comp.std.c++ fixes
> it, because we are not really copying an object representation over in this
> case.

So, can you write a memory allocator which reuses memory in pure
conforming C++ on top of new and delete, or on top of malloc and free?
I'd like to think that you can, and I don't think that you can unless
the following program has no UB.

#include <stdlib.h>
int main()
{
void* p = malloc(sizeof(int) + sizeof(float));
* ( (float*) p ) = 1;
* ( (int*) p ) = 1;
return * ( (int*) p );
}

> Lots of people have different perception of the aliasing rules and of the
> object model. In the GCC PRhttp://gcc.gnu.org/bugzilla/show_bug.cgi?id=29286they did not find


> consensus on whether a plain write would start the lifetime of a new object
> or not.
>
> So what does it do? It probably does the same as in C. In the language
> theory, the above is exactly the reason why unions work in C++. Writes to
> different union members is exactly what starts and stops the lifetime of
> respective member objects and what dictates the "active member" of the
> union. Different people point to bullet 5 of 3.10/15, explaining that
> lifetime stopping or starting would not be needed to make unions work, but
> that's not quite correct. If you access an union member even through a class
> member access expression, the final lvalue doesn't have the type of the
> union. And even if you squint and assume the union lvalue is involved, you
> have two lvalues that are involved, with the member lvalue causing an alias
> conflict.

Yeah. I've never understood what it means to do a read or a write
through lvalue of a POD class type, in C or in C++. (In C++, due to
virtual-funness, it makes sense to talk about reads and writes through
lvalues of non-POD class types.) All you can do with an lvalue of POD
class type is to use a member-of expression to get pointers or lvalues
to its members. All of the actual reads and writes are done through
lvalues of primitive type only.

> In the end here's another interesting difference in C and C++. In C and C++
> the following have different meanings, if you go with the above explanation:
>
>     int a;
>     *(float*)&a = 0;
>
> In C, this violates the aliasing rules and is undefined behavior,

It does? I must be confused then. Ignoring alignment and size issues,
the write ends the lifetime of the int object, and starts the lifetime
of a float object - the effective type rules.

Any further read of 'a' would be a read of a float object through an
int lvalue, which would be UB.

> but in C++
> it won't because as my assumption above states, the C++ model is solely
> based on reads and writes to start and stop the lifetime of objects.

Now I'm really confused. I thought we just agreed that the read and
write rules, aka the effective type rules, are a C thing and not a C++
thing.

> In the
> C++ case this is not necessarily undefined behavior. The size of float may
> be larger than sizeof int or alignment requirements may be incompatible, in
> which case it is undefined behavior. But sizeof(T) and alignment
> requirements are implementation-defined, so this need not necessarily result
> in undefined behavior *for a specific implementation* ("corresponding
> instance" if the abstract machine). So the C++ standard requires the above
> to work for certain implementations.
>
>     int a = 0;
>     float f = *(float*)&a;
>
> This is undefined behavior in both C (same reason as above) and C++ (the
> read won't stop the lifetime of the 'a' object of type int, and then violate
> aliasing rules).

I've always thought about it in the following way. My reading of the
so-called "strict aliasing rules" includes an obvious requirement that
you cannot read an object through a wrongly-typed lvalue. In the above
example, we're reading an int object through a float lvalue, and that
is broken in C and in C++.

> Personally I have given up aliasing and C++, because I feel I lack the
> knowledge of platform details and compiler theory to make up a good
> understanding. I will wait until this is fixed and then read a consistent
> Standard, if that is ever going to happen.

I hope we get such a standard too, but it's not looking likely. Thus
far, I think that the C++ standard is going even further in the wrong
direction with that one suggested fix to the DR. "Allocation types"?
Ugg.

Johannes Schaub (litb)

unread,
Jan 25, 2011, 12:39:54 AM1/25/11
to
Joshua Maurice wrote:

No piece of memory (or piece of storage, for that matter) ever has a
specific type, so that point of view is wrong. The spec says when defining
"object":

"NOTE When referenced, an object may be interpreted as having a particular
type; see 6.3.2.1."

And

"The effective type of an object for an access to its stored value is the

declared type of the object, if any. [...]".

So all you have is a type that an access to an object may have, and a type
that an lvalue refering to an object may have and a type that you may have
declared an object to have. But the object will not have a type at runtime.

The C rationale document is clear on that too

"The definition of object does not employ the notion of type. Thus an
object has no type in and of itself. However, since an object may only be
designated by an lvalue (see §3.2.2.1), the phrase ``the type of an object''
is taken to mean, here and in the Standard, ``the type of the lvalue
designating this object,'' and ``the value of an object'' means ``the
contents of the object interpreted as a value of the type of the lvalue
designating the object.''"

> Another reply was that the above program has perfectly well defined
> behavior, but the following has undefined behavior:
> #include <stdlib.h>
> int foo(int* a, float* b)
> {
> *a = 1;
> *b = 1;
> return *a;
> }
> int main()
> {
> void* p = malloc(sizeof(int) + sizeof(float));
> foo((int*)p, (float*)p);
> }
> Specifically, this example explains how the compiler might use
> aliasing analysis for optimization purposes. A conforming compiler may
> not simply assume that an int* and a float* do not alias. However, if
> analysis shows that aliasing would result in UB (as it would in the
> above program when "return *a;" reads a float object through an int
> lvalue) then the compiler is free to do whatever it wants in the face
> of the UB, including assume that they don't alias.
>

Disregarding of what compilers do, I think your last reply is what is
correct. The initial example had fine behavior, but this example is
undefined behavior, for the exactly reasons you give.

Johannes Schaub (litb)

unread,
Jan 25, 2011, 1:04:42 AM1/25/11
to
Joshua Maurice wrote:

> On Jan 24, 8:33 pm, "Johannes Schaub (litb)" <schaub-johan...@web.de>
> wrote:
>> C++ doesn't have the concept of effective types, and the following is my
>> personal perception of the issue. Rather, in C++ the objects have type
>> themselfs, while in C types are merely an attribute of the access to
>> objects. So an object cannot exist without a type in C++; it wouldn't
>> make sense with the current model. The behavior of the above code is not
>> clearly defined in C++. Not even that other DR we talked about in
>> comp.std.c++ fixes it, because we are not really copying an object
>> representation over in this case.
>
> So, can you write a memory allocator which reuses memory in pure
> conforming C++ on top of new and delete, or on top of malloc and free?
> I'd like to think that you can, and I don't think that you can unless
> the following program has no UB.
>
> #include <stdlib.h>
> int main()
> {
> void* p = malloc(sizeof(int) + sizeof(float));
> * ( (float*) p ) = 1;
> * ( (int*) p ) = 1;
> return * ( (int*) p );
> }
>

As per my assumptions this is fine in C++. To be sure you can use placement
new, because that is explicitly mentioned as one of the ways to create a new
object at 1.8p1.

To be clear: I don't think the spec accurately describes the behavior when
you use plain writes. As you pointed out on comp.std.c++, it currently says
that merely having storage that fits in size and alignment is enough to
start lifetime. This just makes no sense.

On the other side, 3.8 can be interpreted the following way:

- An object of type T exist which was created by one of the ways described
by 1.8p1, but has not yet necessarily started lifetime yet.
- If T is a non-class type or a type with a trivial ctor, lifetime started
at that point. Otherwise, the ctor must first be called and completed.

Now this makes perfect sense for declared objects and objects created by
new-expressions or by temporaries. But it doesn't make sense if you try to
apply this model to malloc/free, because 1.8p1 doesn't define what type such
an object has. See this one:
https://groups.google.com/group/comp.std.c++/browse_thread/thread/9e0bf8134fbe7d09/2c82227634f15895

>> In the end here's another interesting difference in C and C++. In C and
>> C++ the following have different meanings, if you go with the above
>> explanation:
>>
>> int a;
>> *(float*)&a = 0;
>>
>> In C, this violates the aliasing rules and is undefined behavior,
>
> It does? I must be confused then. Ignoring alignment and size issues,
> the write ends the lifetime of the int object, and starts the lifetime
> of a float object - the effective type rules.
>
> Any further read of 'a' would be a read of a float object through an
> int lvalue, which would be UB.
>

The spec says that only of objects that have no declared type. In this case,
we declared it to be an int, so the effective type doesn't change.

"The effective type of an object for an access to its stored value is the

declared type of the object, if any. If a value is stored into an object
having no declared type [...]".

Joshua Maurice

unread,
Jan 25, 2011, 1:01:18 AM1/25/11
to
On Jan 24, 7:15 pm, Ben Bacarisse <ben.use...@bsb.me.uk> wrote:
> <snip much more C++ specific questions>

Let me phrase it as a C /and/ C++ question then. Consider the
following program and questions, in the context of C and C++, because
I don't know the answer in the context of either language.

#include <stdlib.h>

typedef struct T1 { int x; int y; } T1;
typedef struct T2 { int x; int y; } T2;

int main()
{


void* p = 0;
T1 * t1 = 0;
T2 * t2 = 0;
int * x = 0;

if (sizeof(T1) != sizeof(T2))
return 1;

if ( (char*)(& t1->y) - (char*) (t1) != (char*)(& t2->y) -
(char*) (t2) )
return 1;

p = malloc(sizeof(T1));
/* Do we have a T1 object here? Presumably no. Otherwise we also
have a T2 object here, and we definitely don't want to start talking
about two distinct complete objects occupying the same storage at the
same time. */

t1 = (T1*) p;
/* T1 object yet? Presumably the answer hasn't changed since the
above comment. */

x = & t1->x;
/* T1 object yet? */

*x = 1;
/* Do we have a T1 object here? Maybe. I just see a write through
an int lvalue. I see no writes nor reads through a T1 lvalue. I see
nothing that favors T1 over T2, besides some sort of data dependency
analysis through the member-of operator. However, there isn't even a

hint of data dependency analysis in either standard with regards to
object lifetime rules. */

x = & t1->y;
*x = 2;
/* Do we have a T1 object here? The answer must be yes, or we'll
never have a T1 object. However, again, I see nothing to favor having
a T1 object over a T2 object besides data dependency analysis through
the member-of operator. */

t2 = (T2*) p;
return t2->y; /* Is this UB? Why? Why is reading "t1->y" not UB,

Joshua Maurice

unread,
Jan 25, 2011, 1:03:30 AM1/25/11
to
On Jan 24, 10:04 pm, "Johannes Schaub (litb)" <schaub-johan...@web.de>

wrote:
> Joshua Maurice wrote:
> > On Jan 24, 8:33 pm, "Johannes Schaub (litb)" <schaub-johan...@web.de>
> >> int a;
> >> *(float*)&a = 0;
>
> >> In C, this violates the aliasing rules and is undefined behavior,
>
> > It does? I must be confused then. Ignoring alignment and size issues,
> > the write ends the lifetime of the int object, and starts the lifetime
> > of a float object - the effective type rules.
>
> > Any further read of 'a' would be a read of a float object through an
> > int lvalue, which would be UB.
>
> The spec says that only of objects that have no declared type. In this case,
> we declared it to be an int, so the effective type doesn't change.
>
> "The effective type of an object for an access to its stored value is the
> declared type of the object, if any. If a value is stored into an object
> having no declared type [...]".

Mmm. I see. Thank you.

Not that I would actually write this code, but I'm curious what I have
to work with when sorting through both standards tying to come up with
reasonable interpretations.

James Kanze

unread,
Jan 25, 2011, 4:41:30 AM1/25/11
to

Let's hope so:-).

Seriously, in C++ at least, a POD "exists" as soon as the memory
for it is allocated. I think the standard could be clearer, but
I'm pretty sure that the intent is that memory allocated with
malloc (or with the operator new function) is potentially an
object of any POD type which fits, and becomes an object of
a specific POD type when it is used as such. The assignment to
*x means that the memory between p and p + sizeof(int) contains
an int object (and that using it as any other type of object is
undefined behavior).

Again, an interesting case is something like:

void f(float v)
{
void* p = malloc(sizeof(float));
memcpy(p, &v, sizeof(float));
void* pf = (float*)p;
printf("%.5f\n", *pf);
}

What is the type of the object starting at p? (IIRC, the
specification of memcpy says that it copies "as if" through
unsigned char*, so we've effectively used the object as an
unsigned char[]. So using it as a float would seem to violate
§3.10/15 (in the C++ standard). IMHO, the above *should* be
legal and well defined; i.e. if I call f(3.14159), the above
should display "3.14159". But I'm not sure that this is the
case as the standard is currently written. §3.8 is very clear
that the lifetime of an object of type T begins when storage
with the proper alignment and size is obtained, but with malloc
or the operator new function (both of which return a void*, and
guarantee alignment sufficient for any type of object), what is
the type of the object whose lifetime has just begun?

> What about the following?

> #include <cstdlib>
> using namespace std;
> int main()
> {
> void* p = malloc(sizeof(int) + sizeof(float));
> int*x = (int*) p;
> *x = 1;
> float* y = (float*) p;
> *y = 1;
> }

I think this is covered by the last bullet in §3.8: "The
lifetime of an object of type T ends when: [...]-- the storage
which the object occupies is reused or released." Although it's
not nearly as clear as it should be, I would consider the last
assignment as "reusing" the storage as a float, so that the int
object at p ceases to exist, and a new object with float type
comes into existance. In the case of memory obtained by means
of malloc or the operator new function, I think the standard
*should* say (but doesn't) that an object of type T only begins
to exist when the raw memory is initialized as a type T, and
that reinitializing it with a different type causes a new object
to begin to exist. In sum, such memory behaves very much like
a union of all types which fit, and you can only access the last
assigned value. (But you also need special wording for cases
where the "object" is initialized using memcpy or something
similar---writing through an unsigned char*. While the current
wording seems defective, it's hard to find adequate wording.)

> I've had a thread up on comp.std.c++ for a while now about these
> issues, and I've gotten 0 replies. It's quite frustrating.

> In short, I would argue that both of the above programs have no UB in C
> ++, nor their equivalent program in C. You need both programs above to
> have no UB in order to have user-space memory allocators in standard
> conforming C++. I think that the standard's intent is not to forbid
> user-space C++ standard conforming pooling memory allocators.

> Let's look at "3.8 Objectlifetime / 1, 2, 4, 5, 6, and 7". Each of
> those sections make reference to "reusing storage", something which is
> distinct from "releasing the storage". "Reusing the storage" of an
> object ends that object's lifetime. What else can this mean besides
> the following?

> void* p = malloc(sizeof(int) + sizeof(float));
> int*x = (int*) p;
> *x = 1;
> float* y = (float*) p;
> *y = 1; /* reuse of storage, the int object's lifetime ends, and
> the float object's lifetime begins */

Agreed. The real questions are: what is the type of the object
between the malloc and the *x = 1 statement (since an object
lifetime has apparently begun, according to §3.8), and (more
importantly) what about the case where you initialize using
something like memcpy?

> Furthermore, let's look at the rules in "3.8 Object Lifetime". "3.8
> Object Lifetime / 1" is actually nonsensical as written. Consider:
> void* p = malloc(sizeof(char))
> Well, we've allocated storage with proper alignment and type for an
> arbitrarily large number of types, and if those types have a trivial
> constructor, such as:
> struct T1 {};
> struct T2 {};
> struct T3 {};
> //etc.
> then an object of each of those types exists at that location. So, an
> arbitrarily large number of distinct complete objects coexist in "*p"
> according to that reading of the rules, which is entirely
> nonsensical.

I see we're thinking along the same lines.

> Unfortunately, as I've expounded at length in the thread on

> comp.std.c++, the sensible way forward isn't clear. However,


> some of the proposed changes to C++0x in 3.10 / 15 are taking
> the language in quite the wrong direction IMO.

> We need to solve a couple of basic problems. The most important and
> basic is: when does the lifetime of a POD class even begin?

That's a good question: do PODs have lifetime? I'd argue yes,
but it's not the lifetime defined in §3.8. Accessing an
uninitialized POD is undefined behavior, and if you can't access
an object, how can you say it exists?

> Consider:

> #include <cstdlib>
> using namespace std;

> struct T1 { int x; int y; };
> struct T2 { int x; int y; };

Just a note: the answers in the following may differ between
C and C++. I don't have a copy of the C standard handy to
verify what it says, but it does use a subtly different
definition of type than C++, with terms like "compatible types".
(If memory serves me correctly, I think that if two structs both
have a tag, and the tag is different, then the types are not
compatible, and so the effect is the same here. But I'm far
from sure.)

Gcc may be basing its decision on the meaning of "compatible
type" in C. Again, purely from memory (perhaps someone from the
C group could confirm), I think that given:
typedef struct { int i; } T1;
typedef struct { int i; } T2;
, in C, T1 and T2 are "compatible types", and behave more or
less as if they were the same type. (In C++, they are two
distinct types.)

--
James Kanze

James Kanze

unread,
Jan 25, 2011, 5:00:30 AM1/25/11
to
On Jan 25, 3:15 am, Ben Bacarisse <ben.use...@bsb.me.uk> wrote:
> This is cross posted and I don't think the C side of the questions have
> been properly answered. My answer is about C only.

Hopefully, C++ says the same thing. This is one point where
I don't think the languages should differ.

> Joshua Maurice <joshuamaur...@gmail.com> writes:
> > On Jan 24, 2:49 am, James Kanze <james.ka...@gmail.com> wrote:
> >> On Jan 22, 10:29 pm, Joshua Maurice <joshuamaur...@gmail.com> wrote:
> >> > Let's consider this function though:
> >> > int foo(int* x, short* y)
> >> > {
> >> > *x = 1;
> >> > *y = 2;
> >> > return 1;
> >> > }
> >> > int bar(int* x, short* y)
> >> > {
> >> > *x = 1;
> >> > *y = 2;
> >> > return *x;
> >> > }
> >> > Let's consider functions foo and bar. Let's suppose that x and
> >> > y alias in both. For function foo, there is no undefined
> >> > behavior even though both alias (at least according to what
> >> > appears to be the prominent interpretation of these rules).

> >> I'm not sure about C here, but in C++, there is definitely
> >> undefined behavior in foo if x and y alias.

> If x and y both point to the same allocated object, then neither
> function is undefined. The assignments set the "effective type" of the
> allocated object.

Gcc treates it as undefined behavior if there is aliasing, and
may reorder the assignments. (IMHO, this is an error, but from
what I understand, it is an important optimization in certan
cases.)

> >> In fact, there
> >> would be undefined behavior even if foo were simply:

> >> void foo(const int* x, const short* y)
> >> {
> >> printf("%d, %d\n", *x, *y)
> >> }

> Again, this is not always UB when the object being aliased is allocated
> rather than declared.

I'm not sure I understand. Supposing that x and y point to the
same address, which was obtained by malloc. If the memory is
uninitialized, there is undefined behavior. If the memory was
initialized as an int, then accessing it as a short is undefined
behavior, and if it was initialized as a short, accessing it as
an int has undefined behavior. And there's no way for its
"effective type" to be both short and int; it's one or the
other (or none of the above), but it can't be both.

> When the aliased object is allocated, whether the
> accesses are defined or not depends on the effective type of the aliased
> allocated object. To be certain of UB when the pointers point to the
> same allocated object you need something like this:

> void foo(int *x, short *y)
> {
> *y = 1;
> printf("%d\n", *x);
> }

> The assignment ensures that the effective type of the allocated object
> is int so the the second is undefined.

> >> If the two pointers point to the same physical address, there is
> >> no way that the memory they point to can be both an int and
> >> a short.

> In C it can be if the storage is allocated and only stores are done (as
> in the first foo and bar above).

In C (and C++), when the memory is allocated, it is
uninitialized. I don't know what type, if any, it is assumed to
have, but regardless of the type, you simply cannot access
uninitialized memory (except through an unsigned char*). And
once you initialize it, you've fixed the type (until the next
"initialization", at least).

> >> And the C++ standard clearly says:
> >> If a program attempts to access the stored value of an
> >> object through an lvalue of other than one of the following
> >> types the behavior is undefined:
> >> [...]
> >> and short for int or vice versa isn't in the list. And I'm
> >> certain that the intent in C is the same: C definitly allows
> >> trapping representations for integer values, and reading part of
> >> an int as a short could conceivably result in a trapping
> >> representation for a short. (Think of a one's complement
> >> machine which traps on -0.)

> >> The problem becomes more interesting if we replace short with
> >> unsigned char. In that case, my version is legal and defined
> >> behavior: accessing a stored value through an lvalue of char or
> >> unsgiend char type is in the list after the cited paragraph.
> >> (IIRC, in C, this exception only applies to unsigned char; for
> >> some reason, C++ added plain char to the list.)

> In C, the wording is "a character type" which covers char and both
> signed and unsigned char. As you say, it is odd (at last at first
> glance -- I am not a C++ expert) that C++ added char but not signed
> char to the list.

It's especially odd that signed char is allowed, since copying
a signed char cannot necessarily be made to preserve the raw
bits or avoid trapping. (Again, a machine with 1's complement
which either converts all 0's the positive representation when
it sees them, or traps. On such machines, for plain char to
work, it would have to be unsigned---in fact, on the two
machines I know which don't use 2's complement, plain char is
unsigned.)

Just an idea: for purposes of demonstration, it might be better
to use int and float, rather than int and short, because reading
an int as a float can trap on most common machines; we don't
have to introduce such exotics as 1's complement to cause
issues.

--
James Kanze

James Kanze

unread,
Jan 25, 2011, 5:06:45 AM1/25/11
to
On Jan 25, 3:33 am, Joshua Maurice <joshuamaur...@gmail.com> wrote:

[...]


> I'd like to think that the rest of the questions applied to C as well
> as C++.

One would like to think so:-).

I believe that this was the intent in C++98, but the committee,
for whatever reasons, reworded everything, and there are clearly
cases where the resulting wording actually differs in meaning
from that in the C standard. Since C++98, the C committee has
also reworded a lot (things like the representation of integral
types comes to mind). And I'm not sure how seriously the
current C++ committee (and the current drafts) take
C compatibility into account. IMHO: if C++ differs from C where
basic types like int, short and float are involved, it is
a defect in the C++ standard, but I'm not sure that all of the
committee members agree with me.

[...]


> I would also like to hear some of the actual people on the committees
> weigh in on these particular questions.

Formally, I think I'm still a "technical expert" for AFNOR, but
for various personal reasons, I've not been active lately.

--
James Kanze

James Kanze

unread,
Jan 25, 2011, 5:22:40 AM1/25/11
to
On Jan 25, 5:26 am, Joshua Maurice <joshuamaur...@gmail.com> wrote:
> On Jan 24, 8:33 pm, "Johannes Schaub (litb)" <schaub-johan...@web.de>
> wrote:

[...]


> So, can you write a memory allocator which reuses memory in pure
> conforming C++ on top of new and delete, or on top of malloc and free?

If you're actually trying to write an allocator, you also have
to take into account what actual compilers do, and not just the
standard. I seem to recall something along the following lines:

float f(float const* in, bool* out)
{
float result = *in;
*out = true;
return result;
}

failing with g++ when called with:

union U { float f; bool b; };
U u;
u.f = 3.14159;
float g = f(&u.f, &u.b);

According to both C and C++, the union guaranteed that this
should work, But g++ rearranged the read and the write in f.

(I also seem to recall---albeit vaguely---the C committee saying
that it wasn't the intent to make this work; that they only
meant for it to be guaranteed if e.g. f was passed a pointer to
the union. But it's all very vague---I didn't have time to
follow up at the time.)

At any rate, most of this discussion seems to turn around the
same issues, without the union.

--
James Kanze

Joshua Maurice

unread,
Jan 25, 2011, 6:17:56 AM1/25/11
to
On Jan 25, 2:22 am, James Kanze <james.ka...@gmail.com> wrote:
> If you're actually trying to write an allocator, you also have
> to take into account what actual compilers do, and not just the
> standard.  I seem to recall something along the following lines:
>
>     float f(float const* in, bool* out)
>     {
>         float result = *in;
>         *out = true;
>         return result;
>     }
>
> failing with g++ when called with:
>
>     union U { float f; bool b; };
>     U u;
>     u.f = 3.14159;
>     float g = f(&u.f, &u.b);
>
> According to both C and C++, the union guaranteed that this
> should work, But g++ rearranged the read and the write in f.
>
> (I also seem to recall---albeit vaguely---the C committee saying
> that it wasn't the intent to make this work; that they only
> meant for it to be guaranteed if e.g. f was passed a pointer to
> the union.  But it's all very vague---I didn't have time to
> follow up at the time.)
>
> At any rate, most of this discussion seems to turn around the
> same issues, without the union.

Indeed. It's all very related to the union DR. So, the C standard
committee never intended for the following program to have defined
behavior? Interesting.

void foo(int* x, float* y)
{ *x = 1;
*y = 1;
}
int main()
{ union { int x; float y; } u;
foo(&u.x, &u.y);
return u.y;
}

AFAIK, the only way that this could make sense is if you introduce
some formalisms with data dependency analysis.

Let me take another wack at trying to formalize it.

[quote]
For a single function, the compiler may assume that at any particular
point of execution, any accessible pointer value or named variable
does not alias another accessible pointer value or named variable of a
sufficiently different type (see existing strict aliasing rules),
unless the two pointers or named variables have a data dependency
between them (ala the rules for restrict, or maybe the C++0x rules for
std::memory_order_consume). Programs which violate this assumption
have undefined behavior.

Ex:
void foo(int* x, float* y)
{ *x = 1;
*y = 1;
}
int main()
{ union { int x; float y; } u;
foo(&u.x, &u.y);
return u.y;
}
The function foo has a spot during its execution where there are two
accessible pointer values (its parameters x and y) of sufficiently
different types which alias, and there is no data dependency between
in the scope of the body of foo. Thus the assumption is violated, and
the program has undefined behavior.

Ex:
#include <stdlib.h>
int main()
{ int* x = (int*) malloc(sizeof(int));
*x = 1;
free(x);
float* y = (float*) malloc(sizeof(float));
*y = 1;
free(y);
}
In the above program, malloc may return the same piece of memory
twice, once for x, and once for y. However, at no point of execution
are both pointers "live" and pointing to the same piece of memory.
Thus the assumption is not violated, and this program has no undefined
behavior.

Ex:
int main()
{ int x;
float* y;

y = (float*) x;


x = 1;
*y = 2;

x = 3;
}
The above program has a named variable which aliases a pointer value.
However, there exist a data dependency between them, so the program
has no undefined behavior.
[/quote]

I think this is the best I've gotten to formalizing the intent. I'm
deferring to the 'restrict' rules, mostly because I think they would
probably best capture all of the nuances which I need. Perhaps I could
instead use the C++0x data dependency rules ala
std::memory_order_consume. I'm not intimately familiar with those
either.

I'm not sure what I have written thus far is anywhere near sufficient
or correct, but hopefully it captures what I'm aiming for.

The important thing is, AFAIK, nothing like this is in any of the C
standards nor any of the C++ standards.

Joshua Maurice

unread,
Jan 25, 2011, 6:20:09 AM1/25/11
to
On Jan 25, 2:06 am, James Kanze <james.ka...@gmail.com> wrote:
> On Jan 25, 3:33 am, Joshua Maurice <joshuamaur...@gmail.com> wrote:
> > I'd like to think that the rest of the questions applied to C as well
> > as C++.
>
> One would like to think so:-).
>
> I believe that this was the intent in C++98, but the committee,
> for whatever reasons, reworded everything, and there are clearly
> cases where the resulting wording actually differs in meaning
> from that in the C standard.  Since C++98, the C committee has
> also reworded a lot (things like the representation of integral
> types comes to mind).  And I'm not sure how seriously the
> current C++ committee (and the current drafts) take
> C compatibility into account.  IMHO: if C++ differs from C where
> basic types like int, short and float are involved, it is
> a defect in the C++ standard, but I'm not sure that all of the
> committee members agree with me.

That's good to hear. It would be a shame if C++ diverged from C so
drastically. I agree that most C should continue to be legal C++ code
(once you fix up the minor details like the includes, namespaces,
implicit void pointer casts, etc.). Glad you're on the committee.

> > I would also like to hear some of the actual people on the committees
> > weigh in on these particular questions.
>
> Formally, I think I'm still a "technical expert" for AFNOR, but
> for various personal reasons, I've not been active lately.

I never meant to disparage you. I know you are, but up until the last
couple of posts, you didn't really address my interesting questions.
(You have now though, here and else-thread, and thank you again.)

Ben Bacarisse

unread,
Jan 25, 2011, 7:24:01 AM1/25/11
to
James Kanze <james...@gmail.com> writes:

How annoying. I was trying to clarify things and I went and miss-read
the code -- I read the *x return as *y. bar *is* undefined but I was
correct about foo: it is not undefined (in C).

Gcc may re-order the statements because in foo the order does not matter
and in bar the return causes UB.

>> >> In fact, there
>> >> would be undefined behavior even if foo were simply:
>
>> >> void foo(const int* x, const short* y)
>> >> {
>> >> printf("%d, %d\n", *x, *y)
>> >> }
>
>> Again, this is not always UB when the object being aliased is allocated
>> rather than declared.
>
> I'm not sure I understand. Supposing that x and y point to the
> same address, which was obtained by malloc. If the memory is
> uninitialized, there is undefined behavior.

What I should have said is that the code is not always UB. Yes it can
be UB (and it is in almost every imaginable case where you might see
this code) but it does not have to be.

> If the memory was
> initialized as an int, then accessing it as a short is undefined
> behavior, and if it was initialized as a short, accessing it as
> an int has undefined behavior. And there's no way for its
> "effective type" to be both short and int; it's one or the
> other (or none of the above), but it can't be both.

Yes, the effective type can't be two types at once but it can be none.
If the object is zeroed with memset before the call (or it was allocated
using calloc) it still has no effective type (at least this is my
reading of the situation) and so the "effective type of the object is
simply the type of the lvalue used for the access".

>> When the aliased object is allocated, whether the
>> accesses are defined or not depends on the effective type of the aliased
>> allocated object. To be certain of UB when the pointers point to the
>> same allocated object you need something like this:
>
>> void foo(int *x, short *y)
>> {
>> *y = 1;
>> printf("%d\n", *x);
>> }
>
>> The assignment ensures that the effective type of the allocated object
>> is int so the the second is undefined.
>
>> >> If the two pointers point to the same physical address, there is
>> >> no way that the memory they point to can be both an int and
>> >> a short.
>
>> In C it can be if the storage is allocated and only stores are done (as
>> in the first foo and bar above).
>
> In C (and C++), when the memory is allocated, it is
> uninitialized. I don't know what type, if any, it is assumed to
> have, but regardless of the type, you simply cannot access
> uninitialized memory (except through an unsigned char*). And
> once you initialize it, you've fixed the type (until the next
> "initialization", at least).

I don't think memset sets the effective type. A clearer example might
be to use callocd space rather than mallocd space, but in either case if
all you do is stores (like the first foo function above) then C defines
the result. C++'s rules are (naturally) much more complicated and I
don't pretend to understand them yet, but from other posts I gather that
C++ also permits such code -- the write though the pointer effectively
starting a new object lifetime.

What is the effect of memset(..., 0, ...) on such an object in C++? In
C++ what accesses are permitted after zeroing with memset? I think that
in C the effective type is erased (the object will have no effective
type) and the access rules are written to handle that case explicitly.
That wording is not there in C++ and I can't yet work out the
consequences of that difference.

>> >> And the C++ standard clearly says:
>> >> If a program attempts to access the stored value of an
>> >> object through an lvalue of other than one of the following
>> >> types the behavior is undefined:
>> >> [...]
>> >> and short for int or vice versa isn't in the list. And I'm
>> >> certain that the intent in C is the same: C definitly allows
>> >> trapping representations for integer values, and reading part of
>> >> an int as a short could conceivably result in a trapping
>> >> representation for a short. (Think of a one's complement
>> >> machine which traps on -0.)
>
>> >> The problem becomes more interesting if we replace short with
>> >> unsigned char. In that case, my version is legal and defined
>> >> behavior: accessing a stored value through an lvalue of char or
>> >> unsgiend char type is in the list after the cited paragraph.
>> >> (IIRC, in C, this exception only applies to unsigned char; for
>> >> some reason, C++ added plain char to the list.)
>
>> In C, the wording is "a character type" which covers char and both
>> signed and unsigned char. As you say, it is odd (at last at first
>> glance -- I am not a C++ expert) that C++ added char but not signed
>> char to the list.
>
> It's especially odd that signed char is allowed, since copying
> a signed char cannot necessarily be made to preserve the raw
> bits or avoid trapping.

I don't think it's odd, but that is ultimately a matter of opinion. I
think it's simpler to include all char types or, alternatively, to
permit only unsigned char which neither language has done (for entirely
reasonable historical reasons).

> (Again, a machine with 1's complement
> which either converts all 0's the positive representation when
> it sees them, or traps. On such machines, for plain char to
> work, it would have to be unsigned---in fact, on the two
> machines I know which don't use 2's complement, plain char is
> unsigned.)
>
> Just an idea: for purposes of demonstration, it might be better
> to use int and float, rather than int and short, because reading
> an int as a float can trap on most common machines; we don't
> have to introduce such exotics as 1's complement to cause
> issues.

I think traps complicate rather than clarify the argument. It would be
better to restrict the discussion to the access -- is the access itself
permitted or undefined -- rather than add the complication of whether
the result is a trap representation. After all, many accesses that are
unquestionably well-defined can yield a trap representation and many
unquestionably undefined accesses don't involve traps in any way.

To understand C and C++'s access rules I'd suggest we pick examples
where traps don't enter into the picture -- say by using integer types
and declaring that there are no trap representations.

--
Ben.

Ben Bacarisse

unread,
Jan 25, 2011, 8:12:11 AM1/25/11
to
Joshua Maurice <joshua...@gmail.com> writes:

> On Jan 24, 8:10 pm, Ben Bacarisse <ben.use...@bsb.me.uk> wrote:

<snip>


>> Have you got some reason to suspect that there is a problem with any of
>> these programs in C?  The C standard seems quite clear on these specific
>> questions.
>
> Yes. I've been getting various replies when I tweak the above program
> just slightly.
>
> #include <stdlib.h>
> void foo(int* a, float* b)
> {
> *a = 1;
> *b = 1;
> }
> int main()
> {
> void* p = malloc(sizeof(int) + sizeof(float));
> foo((int*)p, (float*)p);
> }
>
> I asked if this had UB in comp.lang.c a while ago. I received various
> replies, with little follow up discussion.
>
> One reply was that a piece of memory may have at most one effective
> type between calls to malloc and free.

That seems to me to be clearly false. Here is the wording:

"The effective type of an object for an access to its stored value is

the declared type of the object, if any[75]. If a value is stored into
an object having no declared type through an lvalue having a type that
is not a character type, then the type of the lvalue becomes the
effective type of the object for that access and for subsequent
accesses that do not modify the stored value. If a value is copied
into an object having no declared type using memcpy or memmove, or is
copied as an array of character type, then the effective type of the
modified object for that access and for subsequent accesses that do
not modify the value is the effective type of the object from which
the value is copied, if it has one. For all other accesses to an
object having no declared type, the effective type of the object is


simply the type of the lvalue used for the access."

Footnote 75 says: "Allocated objects have no declared type."

The object being stored into has no declared type. The *a = 1; makes
the effective type of the allocated space 'int' for that access and for
subsequent accesses that do not modify the object, but *b = 1; does
modify the object so, again, the effective types becomes that of the
lvalue expression used in the store: 'float'.

> Another reply was that this is a DR in the C and C++ language specs,
> known colloquially as the union DR.

There is no union so unless the DR covers more than just unions it won't
apply.

> Another reply was that the above program has perfectly well defined
> behavior, but the following has undefined behavior:

> #include <stdlib.h>
> int foo(int* a, float* b)
> {
> *a = 1;
> *b = 1;
> return *a;
> }
> int main()
> {
> void* p = malloc(sizeof(int) + sizeof(float));
> foo((int*)p, (float*)p);
> }

Yes, that's undefined. Sadly, I miss-read a similar example elsewhere
in this thread. After *b = 1; the effective type is float and the
access through an lvalue expression on type int is undefined.

> Specifically, this example explains how the compiler might use
> aliasing analysis for optimization purposes. A conforming compiler may
> not simply assume that an int* and a float* do not alias. However, if
> analysis shows that aliasing would result in UB (as it would in the
> above program when "return *a;" reads a float object through an int
> lvalue) then the compiler is free to do whatever it wants in the face
> of the UB, including assume that they don't alias.
>
> I think I like the third option best, but my personal preferences
> don't dictate what compilers actually do.

No, nor mine, but that last explanation seems to me to be the correct
one.

An example that might distinguish between a compiler that assumes no
aliasing and one that knows the effective type rules would be this:

#include <stdlib.h>
#include <stdio.h>

int foo(int *a, float *b)
{
int x = *a;
*b = 1;
return x + *b;
}

int main(void)


{
void *p = malloc(sizeof(int) + sizeof(float));

*(int *)p = 1;
printf("%d\n", foo((int *)p, (float *)p));
printf("%f\n", *(float *)p);
return 0;
}

I think James posted a similar example elsewhere. In C this is
well-define and must print 2 and 1.000000 (or thereabouts). If a
compiler just assumes that 'a' and 'b' in foo can never point to the
same object, it might produce the wrong result (by, for example,
optimising 'x' away and using *a in the return).

It is possible that the C committee intended that the rules would allow
a compiler to assume that 'a' and 'b' don't alias, but that is certainly
not how I read the rules as they stand.

--
Ben.

Johannes Schaub (litb)

unread,
Jan 25, 2011, 8:15:19 AM1/25/11
to
Ben Bacarisse wrote:

I don't think so. In foo the order does matter if both x and y point to the
same allocated object. The caller of the function might want to access the
memory later on with a short lvalue. Reordering the assignments will cause
UB in the caller.

>> If the memory was
>> initialized as an int, then accessing it as a short is undefined
>> behavior, and if it was initialized as a short, accessing it as
>> an int has undefined behavior. And there's no way for its
>> "effective type" to be both short and int; it's one or the
>> other (or none of the above), but it can't be both.
>
> Yes, the effective type can't be two types at once but it can be none.
> If the object is zeroed with memset before the call (or it was allocated
> using calloc) it still has no effective type (at least this is my
> reading of the situation) and so the "effective type of the object is
> simply the type of the lvalue used for the access".
>

I didn't consider this at all before. Interesting!

>>> When the aliased object is allocated, whether the
>>> accesses are defined or not depends on the effective type of the aliased
>>> allocated object. To be certain of UB when the pointers point to the
>>> same allocated object you need something like this:
>>
>>> void foo(int *x, short *y)
>>> {
>>> *y = 1;
>>> printf("%d\n", *x);
>>> }
>>
>>> The assignment ensures that the effective type of the allocated object
>>> is int so the the second is undefined.
>>
>>> >> If the two pointers point to the same physical address, there is
>>> >> no way that the memory they point to can be both an int and
>>> >> a short.
>>
>>> In C it can be if the storage is allocated and only stores are done (as
>>> in the first foo and bar above).
>>

A store modifies the effective type of an object. The spec says "If a value

is stored into an object having no declared type through an lvalue having a

type that is not a character type, then the type of the lvalue becomes ...",
therefor if you store an int, and then store a float, the object's effective
type isn't int anymore but becomes a float. It can't be two. At least that
wouldn't make sense to me.

Ben Bacarisse

unread,
Jan 25, 2011, 9:09:08 AM1/25/11
to
"Johannes Schaub (litb)" <schaub-...@web.de> writes:

Good point -- I missed that. I'd word it rather differently though.
The UB (or lack of it) is a property of the text (the program's text)
and not a property that can be caused by what a compiler chooses to do.
I'd phrase it like this: the caller must see the value 2 when accessing
the allocated object via an lvalue expression of type short. If it uses
a char type, it must see (one of) the representations of a short int
with value 2. Any other result means that the implementation is
non-conforming.

Yes, it can't be two at the same time. I hope I did not imply it might
be (though I completely missed the point of the function bar so who know
what impression that gave). Code that only does stores into an
allocated object won't fall foul of the access rules. I think we are in
agreement about that.

--
Ben.

James Kanze

unread,
Jan 25, 2011, 2:34:39 PM1/25/11
to

That's what I vaguely remember. But I don't remember who was
saying this (someone authorized to speak for the committe, or
not?), nor the exact context (other than it was in relationship
with the gcc bug). The best might be to take the discussion to
comp.std.c (although I don't read that).

And once we find out what was intended for C, we then have to
address the issue in C++; I'm still supposing that C++ wants
full C compatibility in this regard. (Or is that wishful
thinking on my part.)

--
James Kanze

Keith Thompson

unread,
Jan 25, 2011, 2:59:46 PM1/25/11
to
James Kanze <james...@gmail.com> writes:
> On Jan 24, 11:44 pm, Joshua Maurice <joshuamaur...@gmail.com> wrote:
[...]

>> We need to solve a couple of basic problems. The most important and
>> basic is: when does the lifetime of a POD class even begin?

Do you mean the lifetime of a POD *object*?

> That's a good question: do PODs have lifetime? I'd argue yes,
> but it's not the lifetime defined in §3.8. Accessing an
> uninitialized POD is undefined behavior, and if you can't access
> an object, how can you say it exists?

At least in C, "access" includes both reading and modifying.
You can certainly modify an uninitialized POD object, something
you couldn't do if it didn't exist.

If you mean the lifetime of the object, why wouldn't it be the lifetime
defined in 3.8? If you mean the lifetime of the class, I'm not sure
what that would mean.

[...]

--
Keith Thompson (The_Other_Keith) ks...@mib.org <http://www.ghoti.net/~kst>
Nokia
"We must do something. This is something. Therefore, we must do this."
-- Antony Jay and Jonathan Lynn, "Yes Minister"

Joshua Maurice

unread,
Jan 25, 2011, 4:05:36 PM1/25/11
to
On Jan 25, 11:59 am, Keith Thompson <ks...@mib.org> wrote:

> James Kanze <james.ka...@gmail.com> writes:
> > On Jan 24, 11:44 pm, Joshua Maurice <joshuamaur...@gmail.com> wrote:
> [...]
> >> We need to solve a couple of basic problems. The most important and
> >> basic is: when does the lifetime of a POD class even begin?
>
> Do you mean the lifetime of a POD *object*?

Yes. Of course. Sorry.

> > That's a good question: do PODs have lifetime?  I'd argue yes,
> > but it's not the lifetime defined in §3.8.  Accessing an
> > uninitialized POD is undefined behavior, and if you can't access
> > an object, how can you say it exists?
>
> At least in C, "access" includes both reading and modifying.
> You can certainly modify an uninitialized POD object, something
> you couldn't do if it didn't exist.
>
> If you mean the lifetime of the object, why wouldn't it be the lifetime
> defined in 3.8?  If you mean the lifetime of the class, I'm not sure
> what that would mean.

I think James was referring to how the lifetime rules for POD objects
are broken in C++. It says the lifetime of a POD object begins as soon
as memory of sufficient size and alignment is allocated, which is
absurd, because that would imply quite a large number of complete
objects of completely different types coexisting in the same piece of
memory for every piece of memory.

I'm not sure of the rules for C. Again, can you answer my questions
else-thread in the example which attempts to distinguish between the
following?
typedef struct T1 { int x; int y; } T1;
typedef struct T2 { int x; int y; } T2;

I think I'll take James's advice and take this to comp.std.c.
Hopefully they're more talkative than comp.std.c++.

Johannes Schaub (litb)

unread,
Jan 25, 2011, 4:35:37 PM1/25/11
to
Joshua Maurice wrote:

T1 and T2 are different types in C. I cannot find a rule that says that
these are compatible. So since they are different types and there is no rule
saying otherwise, the are not compatible. Thus they cannot alias each other.

All of this thread was crossposted to comp.std.c. I installed a followup-to
header that posted all this to comp.lang.c++, comp.lang.c and comp.std.c.

Joshua Maurice

unread,
Jan 25, 2011, 4:45:29 PM1/25/11
to
On Jan 25, 1:35 pm, "Johannes Schaub (litb)" <schaub-johan...@web.de>
wrote:

> Joshua Maurice wrote:
> > I'm not sure of the rules for C. Again, can you answer my questions
> > else-thread in the example which attempts to distinguish between the
> > following?
> >   typedef struct T1 { int x; int y; } T1;
> >   typedef struct T2 { int x; int y; } T2;
>
> > I think I'll take James's advice and take this to comp.std.c.
> > Hopefully they're more talkative than comp.std.c++.
>
> T1 and T2 are different types in C. I cannot find a rule that says that
> these are compatible. So since they are different types and there is no rule
> saying otherwise, the are not compatible. Thus they cannot alias each other.

I can also repeat that verbatim. Can you answer the more specific
questions which I have? I can copy and paste them into a new reply, if
you want.

James Kuyper

unread,
Jan 25, 2011, 8:28:24 PM1/25/11
to
On 01/25/2011 04:05 PM, Joshua Maurice wrote:
> On Jan 25, 11:59�am, Keith Thompson<ks...@mib.org> wrote:
...

>> If you mean the lifetime of the object, why wouldn't it be the lifetime
>> defined in 3.8? �If you mean the lifetime of the class, I'm not sure
>> what that would mean.
>
> I think James was referring to how the lifetime rules for POD objects
> are broken in C++. It says the lifetime of a POD object begins as soon
> as memory of sufficient size and alignment is allocated, which is
> absurd, because that would imply quite a large number of complete
> objects of completely different types coexisting in the same piece of
> memory for every piece of memory.

I think it's clear from the context that this rule does not refer to
arbitrary pieces of memory, but only the particular piece of memory
allocated for that particular object, which has only that object's
specific type.
--
James Kuyper

Joshua Maurice

unread,
Jan 25, 2011, 8:58:12 PM1/25/11
to

Let me ask the relevant questions again then. For the following
program, when does the lifetime of the T1 object begin? Why do we have
a T1 object instead of a T2 object? (See further questions inline in
the code.)

#include <assert.h>
#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* p = 0;
T1* t1 = 0;

T2* t2 = 0;

if (sizeof(T1) != sizeof(T2))
return 1;

if (offsetof(T1, x) != offsetof(T2, x))
return 1;
if (offsetof(T1, y) != offsetof(T2, y))
return 1;

p = malloc(sizeof(T1));
/* Has the lifetime of a T1 object started yet? What about T2? */

*(int*)(((char*)p) + offsetof(T1, x)) = 1;
/* Has the lifetime of a T1 object started yet? What about T2? */

*(int*)(((char*)p) + offsetof(T1, y)) = 2;
/* Has the lifetime of a T1 object started yet? What about T2? */

/* Can one even talk about the start of the lifetime of a T1
object at this point? Is there any reason to prefer talking about a T1
object over a T2 object? How is the above code importantly different
than casting the result of malloc to T1* (which is implicit in C),
using a memberof expression to get an lvalue for both of its members,
and writing to those members? Do we need to change the standards to
explicitly mention that the memberof expression is special with
regards to the object lifetime rules - specifically something like "A
memberof expression "X.Y" starts the lifetime of a new complete object
if there does not exist an object (complete or subobject) of the same
type as X at the memory referred to by X. The new complete object's
type is the same type as the left hand side of the memberof
expression."? I've already mused about such a possibility on comp.std.c
++, but such an idea just strikes me as exceptionally silly. However,
I think that's our only real way out at the moment. */

/* Where is the UB in the following code, for exactly what reason?
*/
t1 = (T1*)p;
printf("%d\n", t1->y);
t2 = (T2*)p;
printf("%d\n", t2->y);
}

Ben Bacarisse

unread,
Jan 25, 2011, 9:15:33 PM1/25/11
to
Joshua Maurice <joshua...@gmail.com> writes:

I am not Johannes Schaub, but I don't mind trying to answer your
specific questions. I think they raise interesting questions and all I
can do is give you my best guess.

Joshua Maurice <joshua...@gmail.com> writes:
| We need to solve a couple of basic problems. The most important and

| basic is: when does the lifetime of a POD class even begin? Consider:

I don't think that's what you want to ask. You seem to be asking when
an allocated object acquires an effective type. In the code below the
lifetime of the allocated object extends from the execution of the
malloc to the program's termination but that's not the important issue.

|
| #include <cstdlib>
| using namespace std;
|

| struct T1 { int x; int y; };
| struct T2 { int x; int y; };


|
| int main()
| {
| void* p = 0;
| T1 * t1 = 0;
| T2 * t2 = 0;
| int * x = 0;
|
| if (sizeof(T1) != sizeof(T2))
| return 1;
| if ( (char*)(& t1->y) - (char*) (& t1) != (char*)(& t2->y) -
| (char*) (& t2) )

You corrected this in a subsequent posting but I think it is better
expressed with offsetof:

if (offsetof(struct T1, y) != offsetof(struct T2, y))

| return 1;
|
| p = malloc(sizeof(T1));
| /* Do we have a T1 object here? Presumably no.

No, there is only an object with no declared type and no effective type.

| Otherwise we also
| have a T2 object here, and we definitely don't want to start talking
| about two distinct complete objects occupying the same storage at the
| same time. */

That does not follow -- having a T1 does not mean we'd have a T2.

memcpy(p, &(struct T1){0, 0}, sizeof (struct T1));

gives the allocated object the effective type "struct T1". In effect
there is now a struct T1 there and nothing else, though that is not a
particularly good way of putting it.

| t1 = (T1*) p;
| /* T1 object yet? Presumably the answer hasn't changed since the
| above comment. */

Agreed.

| x = & t1->x;
| /* T1 object yet? */

In C, your question is a shorthand for "is the effective type of the
allocated object struct T1 yet?". My answer is no I see no reason for
the effective type to have changed.

| *x = 1;
| /* Do we have a T1 object here? Maybe. I just see a write through
| an int lvalue. I see no writes nor reads through a T1 lvalue.

No, I'd say definitely not. We have an int there and that is all. As
you say there is no write through an lvalue expression of type struct
T1.

| I see
| nothing that favors T1 over T2, besides some sort of data dependency
| analysis through the member-of operator. However, there isn't even a
| hint of data dependency analysis in the standard with regards to
| object lifetime rules. */

Talk of object lifetime rules is a C++ issue. In the C version of the
code there is only on object whose lifetime matter here -- the allocated
object and it persists to the end of the program's execution.

| x = & t1->y;
| *x = 2;
| /* Do we have a T1 object here?

I'd say no. I can't see any reason to think otherwise. You reasoning
above (there having been write through an lvalue expression of type
struct T1) still applies.

| The answer must be yes, or we'll
| never have a T1 object.

I don't think so. There are unequivocal way to get a struct T1
object (translation: for the effective type of the allocated object to
be struct T1). One is the memcpy I showed. The other would be a direct
assignment like:

*t1 = (struct T1){0, 0};

But the question you raise is an interesting one. We've set two
sub-objects inside one allocated object (thereby setting the effective
type of those two sub-objects) but we've not set the effective type of
the whole object (at least that's my reading of the standard).

| However, again, I see nothing to favor having
| a T1 object over a T2 object besides data dependency analysis through
| the member-of operator. */

Yes, we have two ints and neither a struct T1 nor a struct T2.

| t2 = (T2*) p;
| return t2->y; /* UB? Why? Why is reading "t1->y" not UB, but
| reading "t2->y" is UB? In other words, why do we have a T1 object, but
| not a T2 object? */

I'd say we have neither.

I have to conclude that neither t1->y not t2->y is undefined. Both
access an object with effective type int though and lvalue expression of
type int.

I say "have to" because I am not sure that this is what was intended.
Having gone round this a few times now I can no longer see the purpose
of the wording relating to aggregates and unions in 6.5 p7. Maybe some
kind c.l.c soul will explain it to me! Or maybe it will be clear to me
in the morning.

| }
|
| Also, what if we used offsetof hackery to initialize both int members
| of the T1 object without using a member-of operator on a T1 lvalue?


--
Ben.

Joshua Maurice

unread,
Jan 26, 2011, 2:51:05 AM1/26/11
to
On Jan 25, 6:15 pm, Ben Bacarisse <ben.use...@bsb.me.uk> wrote:

> Joshua Maurice <joshuamaur...@gmail.com> writes:
> |  
> |     p = malloc(sizeof(T1));
> |     /* Do we have a T1 object here? Presumably no.
>
> No, there is only an object with no declared type and no effective type.
>
> |                                                     Otherwise we also
> | have a T2 object here, and we definitely don't want to start talking
> | about two distinct complete objects occupying the same storage at the
> | same time. */
>
> That does not follow -- having a T1 does not mean we'd have a T2.
>
>   memcpy(p, &(struct T1){0, 0}, sizeof (struct T1));
>
> gives the allocated object the effective type "struct T1".  In effect
> there is now a struct T1 there and nothing else, though that is not a
> particularly good way of putting it.

Of course. I merely meant to argue that the effective type of the
allocated object cannot be T1 just after the malloc. There is
absolutely nothing in the code thus far to favor T1 over T2.

> [...]

> |     x = & t1->y;
> |     *x = 2;
> |     /* Do we have a T1 object here?
>
> I'd say no.  I can't see any reason to think otherwise.  You reasoning
> above (there having been write through an lvalue expression of type
> struct T1) still applies.
>
> |                                      The answer must be yes, or we'll
> | never have a T1 object.
>
> I don't think so.  There are unequivocal way to get a struct T1
> object (translation: for the effective type of the allocated object to
> be struct T1).  One is the memcpy I showed.  The other would be a direct
> assignment like:
>
>   *t1 = (struct T1){0, 0};
>
> But the question you raise is an interesting one.  We've set two
> sub-objects inside one allocated object (thereby setting the effective
> type of those two sub-objects) but we've not set the effective type of
> the whole object (at least that's my reading of the standard).

So, let me ask this then:

#include <assert.h>
#include <stddef.h>
#include <stdlib.h>

typedef struct T1 { int x; int y; } T1;

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

I am correct in assuming that this is a very C idiomatic way of
initializing malloc-ed memory, right?

Is this significantly different than?

#include <assert.h>
#include <stddef.h>
#include <stdlib.h>

typedef struct T1 { int x; int y; } T1;

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

Is one good and the other not? If so, what's the important difference,
and most importantly what part of the standard, if any, can be read to
describe that difference?

James Kanze

unread,
Jan 26, 2011, 4:37:02 AM1/26/11
to
On Jan 25, 7:59 pm, Keith Thompson <ks...@mib.org> wrote:

> James Kanze <james.ka...@gmail.com> writes:
> > On Jan 24, 11:44 pm, Joshua Maurice <joshuamaur...@gmail.com> wrote:
> [...]
> >> We need to solve a couple of basic problems. The most important and
> >> basic is: when does the lifetime of a POD class even begin?

> Do you mean the lifetime of a POD *object*?

Yes.

> > That's a good question: do PODs have lifetime? I'd argue yes,
> > but it's not the lifetime defined in §3.8. Accessing an
> > uninitialized POD is undefined behavior, and if you can't access
> > an object, how can you say it exists?

> At least in C, "access" includes both reading and modifying.
> You can certainly modify an uninitialized POD object, something
> you couldn't do if it didn't exist.

Accessing an object certainly includes both reading and writing.
Maybe I'm reading too much into it, but the C++ statement talks
about "accessing an object's value"---I'm interpreting this to
mean an lvalue to rvalue conversion.

> If you mean the lifetime of the object, why wouldn't it be the lifetime
> defined in 3.8? If you mean the lifetime of the class, I'm not sure
> what that would mean.

Yes. I'm referring to 3.8 in the C++ standard.

--
James Kanze

James Kanze

unread,
Jan 26, 2011, 4:49:12 AM1/26/11
to
On Jan 26, 2:15 am, Ben Bacarisse <ben.use...@bsb.me.uk> wrote:

> Joshua Maurice <joshuamaur...@gmail.com> writes:
> > On Jan 25, 1:35 pm, "Johannes Schaub (litb)" <schaub-johan...@web.de>
> > wrote:
> >> Joshua Maurice wrote:
> >> > I'm not sure of the rules for C. Again, can you answer my questions
> >> > else-thread in the example which attempts to distinguish between the
> >> > following?
> >> > typedef struct T1 { int x; int y; } T1;
> >> > typedef struct T2 { int x; int y; } T2;

> >> > I think I'll take James's advice and take this to comp.std.c.
> >> > Hopefully they're more talkative than comp.std.c++.

> >> T1 and T2 are different types in C. I cannot find a rule
> >> that says that these are compatible. So since they are
> >> different types and there is no rule saying otherwise, the
> >> are not compatible. Thus they cannot alias each other.

The interesting question in C is are they still not compatible
types if you drop the tags after the struct? (In C++, they are,
because in C++, if a class or struct doesn't have a tag, and
is in a typedef, the first name given for the type in the
typedef acts effectively as if it were a tag.)

> | We need to solve a couple of basic problems. The most important and
> | basic is: when does the lifetime of a POD class even begin? Consider:

> I don't think that's what you want to ask. You seem to be asking when
> an allocated object acquires an effective type. In the code below the
> lifetime of the allocated object extends from the execution of the
> malloc to the program's termination but that's not the important issue.

He's coming from a C++ background, where an object's lifetime is
related to its type: an object without a type cannot be alive.
But I like the way you describe it.

[...]


> You corrected this in a subsequent posting but I think it is better
> expressed with offsetof:
>
> if (offsetof(struct T1, y) != offsetof(struct T2, y))
>
> | return 1;
> |
> | p = malloc(sizeof(T1));
> | /* Do we have a T1 object here? Presumably no.

> No, there is only an object with no declared type and no effective type.

Is that directly from the C standard? (I like the way it is
expressed. I think it's the clearest exposition of what I think
should be the specification.)

[...]


> | nothing that favors T1 over T2, besides some sort of data dependency
> | analysis through the member-of operator. However, there isn't even a
> | hint of data dependency analysis in the standard with regards to
> | object lifetime rules. */

> Talk of object lifetime rules is a C++ issue. In the C version of the
> code there is only on object whose lifetime matter here -- the allocated
> object and it persists to the end of the program's execution.

Yes. When you have constructors and destructors, object
lifetime has a real signification. The problem in the C++
standard is that it tries to extend the term to objects without
constructors and destructors, in a way that probably doesn't
apply.

[...]


> Yes, we have two ints and neither a struct T1 nor a struct T2.

> | t2 = (T2*) p;
> | return t2->y; /* UB? Why? Why is reading "t1->y" not UB, but
> | reading "t2->y" is UB? In other words, why do we have a T1 object, but
> | not a T2 object? */

> I'd say we have neither.

> I have to conclude that neither t1->y not t2->y is undefined. Both
> access an object with effective type int though and lvalue expression of
> type int.

The expression t1->y is, by definition, the same as (*t1).y.
And what is (*t1), if not an access to an object of T1 (that, we
agree, doesn't exist).

--
James Kanze

Joshua Maurice

unread,
Jan 26, 2011, 6:10:06 AM1/26/11
to
On Jan 26, 1:49 am, James Kanze <james.ka...@gmail.com> wrote:
> The expression t1->y is, by definition, the same as (*t1).y.
> And what is (*t1), if not an access to an object of T1 (that, we
> agree, doesn't exist).

Let me try a devil's advocate position.

With regards to the current POSIX pthreads rules, and future C++0x
threading rules, is (*t1) a read or write for the purposes of
synchronization and race conditions? I would argue no.

Consider the initial conditions:


typedef struct T1 { int x; int y; } T1;

T1* t = malloc(sizeof(T1));


t->x = 1;
t->y = 2;

If we start off the following two threads simultaneously, we do not
have a race condition under any sane interpretation.
/* thread 1 */
printf("%d\n", t->x);

/* thread 2 */
t->y = 3;

As you noted, those threads are by definition equivalent to:
/* thread 1 */
printf("%d\n", (*t).x);

/* thread 2 */
(*t).y = 3;

Thus, what exactly does that "*t" mean? One of those expressions
clearly involves a read of the *t object (and/or one of its sub-
objects), and the other clearly involves a write of the *t object (and/
or one of its sub-objects).

I think under the traditional view, it's not a race condition because
we're only accessing two distinct sub-objects and never accessing the
complete object.

However, you called *t an access of the T1 object, the object with
effective type T1, whatever. I suppose we could consider all accesses
of the form *t for struct types to be reads and never writes, but I
think that's somewhat silly. I think you would be hard pressed to
defend a terminology which says that "t->y = 3" involves a read of the
*t object.

Alternatively, we could define "access" so that you can access an
object without reading or writing it - for whatever that would mean,
which I think is even sillier.

I don't think that the expression "*t", where t has a not primitive
type, is necessarily an access in the conventional sense.
Unfortunately, I don't like that conclusion.

Consider:
T1 * x;
T1 * y;
/* .. */
*x = *y;
In the last line above, *x and *y are both "involved" in accesses.
There is a read of the *y object, and there is a write to the *x
object.

Consider:
T1 * x;
T1 * y;
/* .. */
(*x).x = (*y).x;
In the last line above, there is still a read and a write, but now
it's a read of a member sub-object and a write to a member sub-object
- not the whole object. The obviousness of this comes from how this
interacts with threading (described above), and with volatile
(described below).

Thus, it seems that "*x" is sometimes a read of the whole object, and
sometimes it's just part of an expression to get an lvalue to one of
its sub-objects (not meant to be an exhaustive list of possible
uses).

I think the rules concerning volatile also show this clearly. Consider
the following:
#include <stdlib.h>
typedef struct T3 { volatile int x; volatile int y; } T3;
int main()
{
T3 * t = malloc(sizeof(T3));

(*t).x = 1; /* Ok. There was a write to the volatile x sub-object.
There was not a read of the volatile x sub-object, and there was
definitely not a write nor read of the volatile y sub-object. I also
argue that there wasn't a read nor write of the T3 object. */

*t = *t; /* Ok, here we have a read of both volatile sub-objects,
and a write to both volatile sub-objects. I would even call this a
read of the T3 object, and a write to the T3 object. */

*t; /* What does this do? I don't know. Is it required to read
both volatile members? Is it required to read neither? */
}

What if the T3 object itself was volatile qualified?
typedef struct T3 { volatile int x; volatile int y; } T3;
int main()
{
volatile T3 a;
volatile T3 * b = & a;
(*b).y = 2;
}
In the above program, is there a read or write to the x sub-object? I
would assume no. Moreover, is there any sort of guaranteed observable
behavior besides the write to the y sub-object? That is, does the top
level volatile in "volatile T3 a;" mean anything in this example?
AFAIK, the top level volatile qualifier here doesn't really do much
because all of the sub-objects are already volatile qualified.

I'm not really sure where I'm going with this though... It's too late
for me. I think I'm trying to get at is AFAIK there really is no such
thing as an access, read or write, of an object with non-primitive
type. All of the accesses are really accesses through primitive types.
This seems abundantly clear from the volatile rules and the rules for
race conditions with threading. Which of course begs the question
about how to make this sensible with the strict aliasing rules which
do talk about accesses of objects of non-primitive type, and the
general consensus which definitely wants to prohibit "accessing" a T1
object through a T2 object lvalue.

Ex: Assuming a compiler which gives these two types the same layout
and size:

typedef struct T1 { int x; int y; } T1;
typedef struct T2 { int x; int y; } T2;

int main()
{
T1 * x;
T2 * y;

x = new malloc(sizeof(T1));
x->x = 1;
x->y = 2;
y = (T2*) x;
return y->y; /* UB */
}

In short: the general consensus says that the above UB is UB because
there's an "access" of an object with effective type T1 through a T2
lvalue. I don't see any rules which explain why the effective type of
the object is T1 (explained in other posts), and I don't see any
meaningful rules which describe what it means to "access" an object
through a T2 lvalue (explained at length in this post).

Willem

unread,
Jan 26, 2011, 6:17:49 AM1/26/11
to
James Kanze wrote:
) The expression t1->y is, by definition, the same as (*t1).y.
) And what is (*t1), if not an access to an object of T1 (that, we
) agree, doesn't exist).

(*t1) is only an access to an object if it is involved
in something else that implies access.

Consider the following: &(*t1)

( Or, when considering structs: &(t1->y) )


SaSW, Willem
--
Disclaimer: I am in no way responsible for any of the statements
made in the above text. For all I know I might be
drugged or something..
No I'm not paranoid. You all think I'm paranoid, don't you !
#EOT

Ben Bacarisse

unread,
Jan 26, 2011, 10:27:42 AM1/26/11
to
James Kanze <james...@gmail.com> writes:

> On Jan 26, 2:15 am, Ben Bacarisse <ben.use...@bsb.me.uk> wrote:
>> Joshua Maurice <joshuamaur...@gmail.com> writes:
>> > On Jan 25, 1:35 pm, "Johannes Schaub (litb)" <schaub-johan...@web.de>
>> > wrote:
>> >> Joshua Maurice wrote:
>> >> > I'm not sure of the rules for C. Again, can you answer my questions
>> >> > else-thread in the example which attempts to distinguish between the
>> >> > following?
>> >> > typedef struct T1 { int x; int y; } T1;
>> >> > typedef struct T2 { int x; int y; } T2;
>
>> >> > I think I'll take James's advice and take this to comp.std.c.
>> >> > Hopefully they're more talkative than comp.std.c++.
>
>> >> T1 and T2 are different types in C. I cannot find a rule
>> >> that says that these are compatible. So since they are
>> >> different types and there is no rule saying otherwise, the
>> >> are not compatible. Thus they cannot alias each other.
>
> The interesting question in C is are they still not compatible
> types if you drop the tags after the struct?

No, I think not. Compatibility is defined by a set of rules and there
are only two might apply. One is "[t]wo types have compatible type if
their types are the same" but the description of a struct declaration
states that it introduces a new type. The second is a rule for
compatibility of structs declared in separate compilation units so it
does not apply here. The two types *would* be compatible if declared in
separate translation units (as you'd expect) but not otherwise.

> (In C++, they are,
> because in C++, if a class or struct doesn't have a tag, and
> is in a typedef, the first name given for the type in the
> typedef acts effectively as if it were a tag.)
>
>> | We need to solve a couple of basic problems. The most important and
>> | basic is: when does the lifetime of a POD class even begin? Consider:
>
>> I don't think that's what you want to ask. You seem to be asking when
>> an allocated object acquires an effective type. In the code below the
>> lifetime of the allocated object extends from the execution of the
>> malloc to the program's termination but that's not the important issue.
>
> He's coming from a C++ background, where an object's lifetime is
> related to its type: an object without a type cannot be alive.
> But I like the way you describe it.
>
> [...]
>> You corrected this in a subsequent posting but I think it is better
>> expressed with offsetof:
>>
>> if (offsetof(struct T1, y) != offsetof(struct T2, y))
>>
>> | return 1;
>> |
>> | p = malloc(sizeof(T1));
>> | /* Do we have a T1 object here? Presumably no.
>
>> No, there is only an object with no declared type and no effective type.
>
> Is that directly from the C standard? (I like the way it is
> expressed. I think it's the clearest exposition of what I think
> should be the specification.)

Yes, pretty much. 6.5 p6 refers to a footnote that explains how some
objects have no declared type ("Allocated objects have no declared
type") and none of the rules that give an object an effective type (that
is a C standard term) apply yet so the object does not yet have one.

> [...]
>> | nothing that favors T1 over T2, besides some sort of data dependency
>> | analysis through the member-of operator. However, there isn't even a
>> | hint of data dependency analysis in the standard with regards to
>> | object lifetime rules. */
>
>> Talk of object lifetime rules is a C++ issue. In the C version of the
>> code there is only on object whose lifetime matter here -- the allocated
>> object and it persists to the end of the program's execution.
>
> Yes. When you have constructors and destructors, object
> lifetime has a real signification. The problem in the C++
> standard is that it tries to extend the term to objects without
> constructors and destructors, in a way that probably doesn't
> apply.
>
> [...]
>> Yes, we have two ints and neither a struct T1 nor a struct T2.
>
>> | t2 = (T2*) p;
>> | return t2->y; /* UB? Why? Why is reading "t1->y" not UB, but
>> | reading "t2->y" is UB? In other words, why do we have a T1 object, but
>> | not a T2 object? */
>
>> I'd say we have neither.
>
>> I have to conclude that neither t1->y not t2->y is undefined. Both
>> access an object with effective type int though and lvalue expression of
>> type int.
>
> The expression t1->y is, by definition, the same as (*t1).y.

Yes, but that's a C/C++ difference. It's not defined like that in C.

> And what is (*t1), if not an access to an object of T1 (that, we
> agree, doesn't exist).

In C, the text describing -> avoids all mention of an access to an object
of the struct type.

However, even if we write (*t1)->y the effect is *still* defined. If
the "whole object" still has no effective type, then the final sentence
of 6.5 p6 applies:

"For all other accesses to an object having no declared type, the


effective type of the object is simply the type of the lvalue used for
the access."

Thus *t1 (and *t2 for that matter) is being "accessed by an lvalue
expression" with "a type compatible with the effective type of the
object". These two quotes are from the wording from the next paragraph
(6.5 p7) that defines what accesses are permitted.

This effective type does not "persist". In the case of a store, the
text of 6.5 p6 reads

"the lvalue becomes the effective type of the object for that access


and for subsequent accesses that do not modify the stored value"

but no such wording applies to a read access. Thus we can even write

return (*t1).y + (*t2).y;

and still be in the land of defined behaviour.

--
Ben.

Keith Thompson

unread,
Jan 26, 2011, 11:38:21 AM1/26/11
to
Joshua Maurice <joshua...@gmail.com> writes:
[...]

> So, let me ask this then:
>
> #include <assert.h>
> #include <stddef.h>
> #include <stdlib.h>
>
> typedef struct T1 { int x; int y; } T1;
>
> int main()
> {
> void* p = malloc(sizeof(T1));
> T1* t = (T1*)p;
> t->x = 1;
> t->y = 2;
> return t->y;
> }
>
> I am correct in assuming that this is a very C idiomatic way of
> initializing malloc-ed memory, right?

There's usually no reason to store the result of malloc in a void*.
This is more idiomatic:

#include <stdlib.h>

typedef struct T1 { int x; int y; } T1;

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

Though personally I'd drop the typedef and refer to "struct T1"
directly.

(Note that the "void" keyword makes the declaration of main a
prototype; it's debatable whether it's necessary, but IMHO it's
at least better style.)

This "sizeof" trick for malloc avoids errors when the type of p changes;
you don't have to mention the type twice. This:

T1 *p = malloc(sizeof(T1));

may be more common out in the real world (outside this newsgroup), but I
prefer the "sizeof *p" trick myself.

> Is this significantly different than?
>
> #include <assert.h>
> #include <stddef.h>
> #include <stdlib.h>
>
> typedef struct T1 { int x; int y; } T1;
>
> int main()
> {
> void* p = malloc(sizeof(T1));
> * (int*) (((char*)p) + offsetof(T1, x)) = 1;
> * (int*) (((char*)p) + offsetof(T1, y)) = 2;
> return ((T1*)p)->y;
> }
>
> Is one good and the other not? If so, what's the important difference,
> and most importantly what part of the standard, if any, can be read to
> describe that difference?

As a matter of style, it's a much more verbose way of saying essentially
the same thing.

Joshua Maurice

unread,
Jan 28, 2011, 6:18:08 PM1/28/11
to
On Jan 26, 8:38 am, Keith Thompson <ks...@mib.org> wrote:
> Joshua Maurice <joshuamaur...@gmail.com> writes:
> > Is one good and the other not? If so, what's the important difference,
> > and most importantly what part of the standard, if any, can be read to
> > describe that difference?
>
> As a matter of style, it's a much more verbose way of saying essentially
> the same thing.

So let me ask again, to you and anyone else. Is there any difference
between the two programs:

#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)
{ T1 *p = malloc(sizeof *p);
p->x = 1;
p->y = 2;
return p->y;
}

and

#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* p = malloc(sizeof(T1));
* (int*) (((char*)p) + offsetof(T1, x)) = 1;
* (int*) (((char*)p) + offsetof(T1, y)) = 2;
return ((T1*)p)->y;
}

Specifically, I presume that everyone agrees C and C++ needs to
support the first program with no UB. The interesting questions I have
concern the second. Does the "return ((T1*)p)->y;" result in UB? Why?
What's the important different between these two programs, and
specifically the parts of the standards which explain the important
differences.

Also, if the second program has no UB, can we instead return "return
((T2*)p)->y;" for implementations which we've tested that T1 and T2
have equivalent layout? That is, it might not be a portable program,
but for those systems which there is no difference in layout, would
the access through T2 have UB? Why?

ptyxs

unread,
Jan 29, 2011, 4:15:32 AM1/29/11
to

Perhaps I missed something, but your first program does'nt compile on
my system :
error: invalid conversion from ‘void*’ to ‘T1*’

Joshua Maurice

unread,
Jan 29, 2011, 5:49:30 AM1/29/11
to

Sorry. I was thinking in C, and in C, void pointers implicitly convert
to any other point. Add an explicit cast to make it legal C and C++
(though very un-idiomatic C), as follows:
{ T1 *p = (T1*) malloc(sizeof *p);

dS...@arcor.de

unread,
Feb 3, 2011, 2:26:56 AM2/3/11
to
On 29 Jan., 00:18, Joshua Maurice <joshuamaur...@gmail.com> wrote:
> ...
> Also, if the second program has no UB, can we instead return "return
> ((T2*)p)->y;" for implementations which we've tested that T1 and T2
> have equivalent layout? That is, it might not be a portable program,
> but for those systems which there is no difference in layout, would
> the access through T2 have UB? Why?

Behavior, upon use of a nonportable program construct, for which the C
Standard imposes no requirements is by definition 3.4.3 so-called
"undefined behavior".
(That doesn't imply that an existing implementation would behave
unpredictable. If you don't care for portability and have found by
inspection of code, data, machine architecture, whatever, that your
program behaves predictable, then don't worry.)

Joshua Maurice

unread,
Feb 3, 2011, 3:02:02 AM2/3/11
to

I would kindly ask you to read the rest of this thread, and realize
that I am quite well versed on the issues at hand, and I understand
that the intent of the C standards committee was to make the following
program have undefined behavior.

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

return ((T2*)p)->y;
}

I don't mean to dispute that's how people understand the standard. I
don't plan on writing any code any time soon that violates the well
understood intent of the standard.

However, that's not the rules as written. What I do want to discuss is
if there's any sensible reading of the standard as written which can
give the desired conclusion, while preserving idiomatic usages of C,
such as casting the return of malloc to a struct type pointer, and
assigning to members of that struct.

I note how you did not answer any of my questions, and instead read
the standard line given to those new to the issues. I understand that
this is a generally acceptable method of imparting information, but it
does not apply in this case.

Again: Which of the following programs have UB as written, 1, 2, both,
neither? Why? Please quote exact parts of the standard (C or C++) with
thorough reasoning. Which of the following would have UB if the return
was replaced with "return ((T2*)p)->y;", 1, 2, both, neither? Why?
Please quote exact parts of the standard (C or C++) with thorough
reasoning.

//program 1


#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)

{ T1 *p = (T1*) malloc(sizeof *p);


p->x = 1;
p->y = 2;
return p->y;
}

//program 2


#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* p = malloc(sizeof(T1));
* (int*) (((char*)p) + offsetof(T1, x)) = 1;
* (int*) (((char*)p) + offsetof(T1, y)) = 2;
return ((T1*)p)->y;
}

PS: I hope the intended answer is both do not have UB as written. I
understand the answer that the first would have UB if the return was
changed to "return ((T2*)p)->y;", but even I cannot grasp at the
straws to come to the conclusion that program 2 would have UB if the
return was changed to "return ((T2*)p)->y;".

Note that the above is all assuming a particular implementation where
sizeof(T1) == sizeof(T1), and offsetof(T1, y) == offsetof(T2, y). I
know it's definitely not portable, but I don't see any /rules as
written/ which demand UB on all platforms. (offsetof(T1, x) == 0 and
offsetof(T2, x) == 0 by an already existing guarantee in the C and C++
standards.)

offsetof is just a macro which evaluates to an integer. So what that I
passed T1 to it. It shouldn't matter. I should be able to hardcore 4
in place of offsetof(T1, y) and offsetof(T2, y) and expect it to work
on some platforms, like the common x86 win32. I see nothing in program
2 that says we have an object of type or effective type T1 nor T2. I
see only writes through int lvalues. Moreover, I see little to no
difference between program 1 and program 2 in this regard - I see
little reason to talk about an object with type or effective type T1
nor T2 in program 1.

This is especially true in light of the rules for volatile, and the
rules of POSIX, win32 (maybe?) and C++0x threading, which heavily
interact with the definition of "access". What does "access" mean? I
would argue that if that word is to have any meaning, it means exactly
a read, a write, or both. What does it mean to access an object of
struct type with a member expression, ex: "x.y"? It definitely doesn't
read the full struct x, nor write the full struct x. Hell, it doesn't
even imply a read or a write, ex: "int * a = & x.y;". From our well
understood knowledge of threading, that is neither a read nor a write
of "x" nor "x.y". So, what can the strict aliasing rules say about
this? In the above programs, there is no single expression which we
can say "accesses" an object through a T1 nor T2 lvalue, unless we
want to start using two different contradictory definitions of the
word "access". The same conclusion can be reached through a discussion
of the observable behavior requirements of volatile objects.

Let me again emphasis that I don't plan to write production code like
this ever, but these simple examples elucidate the actual scope and
effect of the standards, such as the proper and correct way to write a
pooling memory allocator on top of malloc or the new operator.
Specifically, at least in the C++ case, we need to know when the
lifetimes of objects begin and end, and what it means to access an
object through an incorrectly typed lvalue.

Again, finally, as far as I can see, the only way to make program 1
have UB with the T2 cast return is to invent some rules which
explicitly mention data dependency analysis in the effective type
rules of C, in the object lifetime rules of C++ (or just copy the
effective type rules of C for POD types into C++), and in the allowed
lvalue access rules, aka the strict aliasing rules of C and C++. When
you consider the implications raised with volatile and threading which
heavily interact with the definition of "access", this seems like the
only way out.

Or, if you can prove me wrong, and help me understand an error of
mine, please do so.

dS...@arcor.de

unread,
Feb 4, 2011, 10:32:10 AM2/4/11
to
(Since my comment only applies to C, I removed comp.lang.c++ from the
newsgroups list.)

On 3 Feb., 09:02, Joshua Maurice <joshuamaur...@gmail.com> wrote:
> ...

> I would kindly ask you to read the rest of this thread, and realize
> that I am quite well versed on the issues at hand, and I understand
> that the intent of the C standards committee was to make the following
> program have undefined behavior.
>
>   #include <stdlib.h>
>   typedef struct T1 { int x; int y; } T1;
>   typedef struct T2 { int x; int y; } T2;
>   int main(void)
>   { T1 *p = malloc(sizeof *p);
>     p->x = 1;
>     p->y = 2;
>     return ((T2*)p)->y;
>   }
>
> I don't mean to dispute that's how people understand the standard. I
> don't plan on writing any code any time soon that violates the well
> understood intent of the standard.
>
> However, that's not the rules as written. What I do want to discuss is
> if there's any sensible reading of the standard as written which can
> give the desired conclusion, while preserving idiomatic usages of C,
> such as casting the return of malloc to a struct type pointer, and
> assigning to members of that struct.

For preserving idiomatic usages of C, such as casting the return of


malloc to a struct type pointer, and assigning to members of that

struct, it is not necessary that the above program's behavior were
defined, because the program construct which makes the standard impose
no requirements for the above program's behavior is not one of the
aforementioned.

Unfortunately, at the moment I haven't the time to adress your further
concerns - maybe next week.

Joshua Maurice

unread,
Feb 5, 2011, 5:22:28 PM2/5/11
to

I'm sorry, I don't quite follow your English. It is idiomatic usage in
C to implicitly cast the return of malloc to a struct pointer, then,
assign to members of the struct through that pointer, then read or
write those members through the pointer. My program 1 does exactly
that. (Well, it has an explicit cast instead of implicit to make the
code valid C++ as well, but minor point.) Thus the C (and C++)
standard ought to say that program 1 as written has no UB.

> Unfortunately, at the moment I haven't the time to adress your further
> concerns - maybe next week.

Thank you for your time.

In short, I think my "interesting" questions are: For a platform where
T1 and T2 have the same layout (aka this is not a question about
portable semantics):

- Do programs 1 and 2 have any UB as written? I presume that the
answer is no UB in either as written.

- Would program 2 have UB if the return was changed to "return
((T2*)p)->y;" ? Given no UB before, this change cannot introduce UB.

- Would program 1 have UB if the return was changed to "return
((T2*)p)->y;" ? Now, here's my problem.

Under any sane standard, "program 2 as written" and "program 2 with
T2* cast return" must both be defined, or both have UB. Also under any
sane standard, "program 1 as written" must not have UB.

I think our remaining options are:

- "program 1 as written", "program 2 as written", "program 1 with T2*
cast return", and "program 2 with T2* cast return", all do not have
UB. This is a problem because this is in direct contradiction with the
common understanding of the standard and the intent of the standard
that "program 1 with T2* cast return" has UB.

- "2 as written" and "2 with T2* cast return" have UB.

I also do not like this conclusion because I don't see any important
difference between:
p->y = 2;
and


* (int*) (((char*)p) + offsetof(T1, y)) = 2;

Both are simply writes through int lvalues. I could remove the
offsetof macro and hardcode "4", thereby removing any reference to the
type T1 in that expression, ala:
* (int*) (((char*)p) + 4) = 2;
I see nothing in the C (nor C++) standard which gives me any reason to
think that these assignment expressions are anything but entirely
equivalent. Again, I do not mean to claim that the code is /portable/,
but for those implementations for which the layout is like this, it
should not have UB.

- "program 1 as written" has no UB, and the rest have UB. I do not
like this conclusion for the same reasoning as above, namely I do not
see an important difference between:
p->y = 2;
and

Stephen Sprunk

unread,
Feb 5, 2011, 5:34:10 PM2/5/11
to
On 05 Feb 2011 16:22, Joshua Maurice wrote:
> I'm sorry, I don't quite follow your English. It is idiomatic usage in
> C to implicitly cast the return of malloc to a struct pointer, then,

There is no such thing as an "implicit cast". A "cast" is an explicit
conversion; an implicit conversion is one done without casting.

S

--
Stephen Sprunk "God does not play dice." --Albert Einstein
CCIE #3723 "God is an inveterate gambler, and He throws the
K5SSS dice at every possible opportunity." --Stephen Hawking

Joshua Maurice

unread,
Feb 5, 2011, 5:48:33 PM2/5/11
to
On Feb 5, 2:34 pm, Stephen Sprunk <step...@sprunk.org> wrote:
> On 05 Feb 2011 16:22, Joshua Maurice wrote:
>
> > I'm sorry, I don't quite follow your English. It is idiomatic usage in
> > C to implicitly cast the return of malloc to a struct pointer, then,
>
> There is no such thing as an "implicit cast".  A "cast" is an explicit
> conversion; an implicit conversion is one done without casting.

Mmm. Yes. Thank you.

Joel C. Salomon

unread,
Feb 5, 2011, 7:32:10 PM2/5/11
to
On 02/05/2011 05:22 PM, Joshua Maurice wrote:
> In short, I think my "interesting" questions are: For a platform where
> T1 and T2 have the same layout (aka this is not a question about
> portable semantics):

Would it be fair to rephrase the question as, "Given an
implementation-defined guarantee of X (permitted but not required by the
Standard), are Y and Z necessarily defined as well?"?

--Joel

Joshua Maurice

unread,
Feb 5, 2011, 8:14:31 PM2/5/11
to

Well, it's more like asking "When am I allowed to read a value through
an int lvalue, ex:
T1* p = /* ... */
return p->y;
?"

The entire point of these questions is to ferret out the actual
requirements of the strict aliasing rules and/or the effective type
rules. Why would the return "return ((T1*)p)->y;" not be UB, but
"return ((T2*)p)->y" be UB?

As I described, in program 1 and program 2, I don't see how that is a
read or write through a T1 lvalue, so I don't see how the strict
aliasing rules or the effective type rules apply.

Moreover, why should program 1 create an object with type or effective
type T1? Why not T2? I see no important difference between programs 1
and 2 with regards to discussing the effective type of the object in
the memory returned by malloc, and I clearly see nothing in program 2
which creates a T1 object, but not a T2 object. So, something's got to
give. Either:

1-


p->y = 2;
and
* (int*) (((char*)p) + offsetof(T1, y)) = 2;

have different semantics, where the first somehow participates in
changing the effective type of the object to T1, or

2- the well understood meaning of the strict aliasing rules and the
effective type rules is not actually specified in the standard.

At least, I think those are our choices. Is there a third option? I
don't see any as being particularly intuitive or "obviously right".

Tim Rentsch

unread,
Feb 5, 2011, 11:04:18 PM2/5/11
to
Joshua Maurice <joshua...@gmail.com> writes:

> [snip]

> standards.) [snip elaboration]

Talking just about C, not C++, let me refer to the four programs as
P1, P2, P1', P2', with the primed versions using a (T2*)-casted
pointer in their 'return' statements (and assuming that layouts and
sizes are consistent).

P1 has defined behavior. I expect everyone agrees on this one.

P2 and P2' must be identical in their definedness. The reason is
there is only one place where a structure type affects (the type of)
an access, and under the stated assumptions the two structure types
are interchangeable. And, since P2-definedness is implied by
P1-definedness, both P2 and P2' have defined behavior.

Now we get to the interesting case, P1'.

For purposes of comp.std.c, I think P1' is undefined behavior
but the Standard doesn't do a really good job of expressing this
clearly. The result _intended_ seems fairly clear (see below) but
unfortunately is stated in a way that is neither precise nor obvious.

For purposes of comp.lang.c, I would simply say P1' is undefined
behavior, because of the rules for how '.' and '->' work. In
particular, consider 6.5.2.3p5 and 6.5.2.3p8. The code for P1' is
roughly analogous to the second code sample in 6.5.2.3p8, which
is stated as being "not a valid fragment". Similar reasoning applies
to P1', and so P1' has undefined behavior.

Johannes Schaub (litb)

unread,
Feb 6, 2011, 10:46:51 AM2/6/11
to
Joshua Maurice wrote:

> On Jan 21, 9:20 am, "Johannes Schaub (litb)" <schaub-johan...@web.de>
> wrote:
>> Ben Bacarisse wrote:
>> > "Johannes Schaub (litb)" <schaub-johan...@web.de> writes:
>>
>> >> Would we be allowed to do this in the opposite direction, if we know
>> >> that the alignment is fine?
>>
>> > What's the opposite direction? Are you asking if changing the int will
>> > change the value of *b? If so, yes (provided the new int value's bits
>> > do indeed affect the byte in question).
>>
>> I mean to ask: If aliasing of an A object by an lvalue of type B is OK,
>> is aliasing of a B object by an lvalue of type A OK?
>
> No. Don't think about it as an aliasing rule. Think about it as a rule
> which restricts the types of lvalues with which you can legally access
> objects.
>
> You can always access an object through a char or unsigned char
> lvalue. (Or maybe it's only for POD types - there's no consensus. I
> would only use char and unsigned char to access POD objects.)
>
> You can always access an object through a base class lvalue, but you
> can never do the reverse: you can never take a complete object of type
> T and access it through a derived type of type T.

You cannot access an object of derived class type and access it as a base
class lvalue either. You always need to point to the proper base class
subobject. If you try to directly access the complete object by a base class
lvalue, you will be lucky if it crashes.

In this sense it's the same for base/derived relationship in both
directions. If the base-class subobject and the complete object have the
same address, you can reinterpret_cast and if you aren't lucky you can
read/write with the resulting lvalue. If you do the proper thing and use an
implicit conversion or an explicit conversion (for the downcast), you have
defined behavior. But that has nothing to do with the aliasing rule. IMO the
respective bullet in 3.10p15 is flawed.

Johannes Schaub (litb)

unread,
Feb 6, 2011, 10:55:19 AM2/6/11
to
Johannes Schaub (litb) wrote:

Having thought about this again, I think the respective bullet is NOT
flawed. The bullet implies that you already have made a successful
conversion and have a proper lvalue.

We do actually have the reverse (access a base class object by the derived
class type), by means of "the dynamic type of the object" (first bullet). It
is catched by that, and to my surprise, if you turn around the bullet about
the base-class subobject rule according to symmetry rule, you get nearly the
same wording

- a type that is the (possibly cv-qualified) dynamic class type of
the type of the object

So I think we again see that the following rule seems to be true:

If aliasing of an A object by an lvalue of type B is OK,
is aliasing of a B object by an lvalue of type A OK?

Please correct me If I'm misunderstanding anything.

Johannes Schaub (litb)

unread,
Feb 6, 2011, 11:06:22 AM2/6/11
to
Johannes Schaub (litb) wrote:

Having thought about this again, I think the respective bullet is NOT

flawed. The bullet implies that you already have made a successful
conversion and have a proper lvalue.

I think we do actually have the reverse (access a base class object by the
derived class type),

- a type that is a (possibly cv-qualified) derived class type of the

dynamic type of the object

Converting from a "Base&" to a "Derived&" is already UB if the type of the
complete object of the object referred to is not of type "Derived" or not of
a type derived from Derived. So this rule too assumes that we have a proper
lvalue, and thus the symmetric equivalent to that bullet as above is true
too.

So I think we again see that the following rule seems to be true:

If aliasing of an A object by an lvalue of type B is OK,


is aliasing of a B object by an lvalue of type A OK?

Please correct me If I'm misunderstanding anything.

Johannes Schaub (litb)

unread,
Feb 6, 2011, 11:27:29 AM2/6/11
to
Joshua Maurice wrote:

Assuming layout and sizes are equal for structurally equal structs, and
assuming the spec is correct:

P1: In the first, no object prior to the first write has a declared type.
After the write, there are two objects that have an effective type of type
int, and the read afterwards is alright.

P2: In the second, the situation until the return statement is exactly
equal. At the return statement, you access the second object whose effective
type is 'int' by an 'int', so you go fine too (exactly like in P1). So this
is fine too.

> Also, if the second program has no UB, can we instead return "return
> ((T2*)p)->y;" for implementations which we've tested that T1 and T2
> have equivalent layout? That is, it might not be a portable program,
> but for those systems which there is no difference in layout, would
> the access through T2 have UB? Why?

Since we assume layout and size is equal, Casting p to T2 will make no
difference. You still access the second int by an int lvalue. So this is
fine too.

Let's make a different program

T1 *p = malloc(sizeof *p);

*p = (T1){ 0, 1 };

Now we have 3 effectively typed objects. The effective type of the first is
T1, and the one of the second and third is int. Given that, the following is
UB:

T2 p1 = *(T2*)p;

Because you violate the aliasing rule, accessing a T1 effectively typed
object by a T2 lvalue.

I think that is what the spec says. And I don't think it agrees with what
the committee says. The committee wants to say that in P1, there exists a T1
object in addition. According to the committee, if you insert a cast in to
T2 lvalue in P1's return statement, result is undefined. But the spec does
not say that.

Johannes Schaub (litb)

unread,
Feb 6, 2011, 11:30:04 AM2/6/11
to
Joshua Maurice wrote:

Assuming layout and sizes are equal for structurally equal structs, and

assuming the spec is correct:

P1: In the first, no object prior to the first write has a declared type.
After the write, there are two objects that have an effective type of type
int, and the read afterwards is alright.

P2: In the second, the situation until the return statement is exactly
equal. At the return statement, you access the second object whose effective
type is 'int' by an 'int', so you go fine too (exactly like in P1). So this
is fine too.

> Also, if the second program has no UB, can we instead return "return


> ((T2*)p)->y;" for implementations which we've tested that T1 and T2
> have equivalent layout? That is, it might not be a portable program,
> but for those systems which there is no difference in layout, would
> the access through T2 have UB? Why?

Since we assume layout and size is equal, Casting p to T2 will make no

difference. You still access the second int by an int lvalue. So this is
fine too.

Let's make a different program

T1 *p = malloc(sizeof *p);


*p = (T1){ 0, 1 };

Now we have 1 effectively typed object, and that object has type T1. Given

Johannes Schaub (litb)

unread,
Feb 6, 2011, 11:55:10 AM2/6/11
to
Johannes Schaub (litb) wrote:

In particular, I think the committee intends the spec to say that a struct
or union access expression involves an access with the struct or union
lvalue.

T1 *p = malloc(sizeof *p);

p->x = 0;

In this case, I think the committee's intent is that the object pointed to
by "p" is accesse by an lvalue of type T1, and so the effective type of the
object containing the int changes to T1. So a later cast and access by an
lvalue of T2 will be undefined behavior.

Joshua Maurice

unread,
Feb 6, 2011, 4:19:45 PM2/6/11
to
On Feb 6, 8:55 am, "Johannes Schaub (litb)"

I think this is also the only sensible interpretation of the
committee's intent. That is
p->y = 2;
is not equivalent to


* (int*) (((char*)p) + offsetof(T1, y)) = 2;

That is, the apparently only sensible way out is: the first somehow
participates in unwritten rules to make a T1 object, and the offsetof
way does not.

I wonder where they want to draw the difference. Let this be the
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* p = malloc(sizeof(T1));

/* ... */
}

Consider the subsequent alterations. Let's start with the simple:
T1* a = (T1*) p;
a->y = 2;
return a->y;
Now, changing it to the following shouldn't give it UB.
T1* a = (T1*) p;
T2* b = (T2*) p;
a->y = 2;
return a->y;
Let's add an explicit temporarily variable as follows.
T1* a = (T1*) p;
T2* b = (T2*) p;
int* a_y = & a->y;
*a_y = 2;
return a->y;
Let's add another variable.
T1* a = (T1*) p;
T2* b = (T2*) p;
int* a_y = & a->y;
int* b_y = & b->y;
*a_y = 2;
return a->y;
Ok, up to this point, I'm pretty sure everyone would agree that we
have no UB. Now, let's take that one dubious step, and transform the
above to:
T1* a = (T1*) p;
T2* b = (T2*) p;
int* a_y = (int*) (((char*)a) + offsetof(T1, y));
int* b_y = (int*) (((char*)b) + offsetof(T2, y));
*a_y = 2;
return a->y;
Quick change, replacing some of the "a" and "b" with "p":
T1* a = (T1*) p;
T2* b = (T2*) p;
int* a_y = (int*) (((char*)p) + offsetof(T1, y));
int* b_y = (int*) (((char*)p) + offsetof(T2, y));
*a_y = 2;
return a->y;
Now we have a problem, because on any sane implementation,
offsetof(T1, y) == offsetof(T2, y), which means for most
implementations I can transform it to:
T1* a = (T1*) p;
T2* b = (T2*) p;
int* a_y = (int*) (((char*)p) + offsetof(T2, y));
int* b_y = (int*) (((char*)p) + offsetof(T2, y));
*a_y = 2;
return a->y;
and reverse the dubious step to get:
T1* a = (T1*) p;
T2* b = (T2*) p;
int* a_y = & b->y;
int* b_y = & b->y;
*a_y = 2;
return a->y;
simplify a bit:
((T2*)p)->y = 2;
return ((T1*)p)->y;
And we're done.

So, that means we need to conclude that:
int* y = & a->y;
is fundamentally different than:
int* y = (int*) (((char*)a) + offsetof(T1*, y));
And I think the only way we can formalize this is to require data
dependency analysis. Let me repeat the first program fragment here:
T1* a = (T1*) p;
a->y = 2;
return a->y;
aka:
T1* a = (T1*) p;
int* y = & a->y;
*y = 2;
return *y;
Our only way out appears to be: we have a object of effective type T1
because of the int write "*y = 2;", and because that int write went
through an int lvalue "*y" / int pointer "y" which was obtained via a
data dependency from a memberof expression on a T1 type lvalue "int* y
= & a->y;".

So, earlier when I was rambling on comp.std.c++ about making memberof
expressions special, I was right in that the only way out. However,
this just strikes me as fundamentally wrong though. I don't like it.

Joshua Maurice

unread,
Feb 6, 2011, 4:23:57 PM2/6/11
to
On Feb 6, 7:46 am, "Johannes Schaub (litb)"
<schaub.johan...@googlemail.com> wrote:

> Joshua Maurice wrote:
> > No. Don't think about it as an aliasing rule. Think about it as a rule
> > which restricts the types of lvalues with which you can legally access
> > objects.
>
> > You can always access an object through a char or unsigned char
> > lvalue. (Or maybe it's only for POD types - there's no consensus. I
> > would only use char and unsigned char to access POD objects.)
>
> > You can always access an object through a base class lvalue, but you
> > can never do the reverse: you can never take a complete object of type
> > T and access it through a derived type of type T.
>
> You cannot access an object of derived class type and access it as a base
> class lvalue either. You always need to point to the proper base class
> subobject. If you try to directly access the complete object by a base class
> lvalue, you will be lucky if it crashes.
>
> In this sense it's the same for base/derived relationship in both
> directions. If the base-class subobject and the complete object have the
> same address, you can reinterpret_cast and if you aren't lucky you can
> read/write with the resulting lvalue. If you do the proper thing and use an
> implicit conversion or an explicit conversion (for the downcast), you have
> defined behavior. But that has nothing to do with the aliasing rule. IMO the
> respective bullet in 3.10p15 is flawed.

Indeed and agreed. Pedantic, but still important. This becomes evident
in multiple inheritance and virtual inheritance cases.

Joshua Maurice

unread,
Feb 6, 2011, 4:33:57 PM2/6/11
to
On Feb 6, 7:55 am, "Johannes Schaub (litb)"

Implicit in that entire piece of standard is that you obtained that
lvalue through a "proper" explicit or implicit conversion or cast. If
you start throwing around reinterpret_casts, then it's quite easy to
break it. Consider:
struct A { int x; };
struct B : A {};
int main()
{
B b;
b.x = 1;
A* a = & b;
return a->x;
}
Now, what's left is quite pedantic, and I'm not sure of the exact
nomenclature. When I access the base class subobject ala "return a-
>x;", is that consider "accessing the stored value of the [derived
class] object" according to the wording of C++03 "3.10 Lvalues and
rvalues / 15" ? I presume yes. Those bullets are there just as
allowance that you /can/ access base class subobjects through base
class type lvalues and through lvalues of the member types, and you
can access the object through the dynamic type of the object. It
doesn't mention that the lvalues must have been properly obtained -
the following is an example of improperly obtaining the lvalue:
int main()
{
B b;
b.x = 1;
A* a = reinterpret_cast<A*>(&b);
return a->x;
}
Is the above UB? I don't know. Maybe? Either way you should never do
it. It definitely is UB if we have virtual or multiple inheritance.
Where is this fundamental distinction mentioned in the standard?
Nowhere where I can see.

> So I think we again see that the following rule seems to be true:
>
>     If aliasing of an A object by an lvalue of type B is OK,
>     is aliasing of a B object by an lvalue of type A OK?
>
> Please correct me If I'm misunderstanding anything.

Well, yes. If you have an A object, and you can access a sub-object of
that, or a containing object of that, through a B lvalue, then you can
definitely take that same B object, and access the corresponding A
object through an A lvalue. Are you trying to say something more?

Johannes Schaub (litb)

unread,
Feb 6, 2011, 5:36:03 PM2/6/11
to
Joshua Maurice wrote:

I think I'm missing something. This last simplification does not seem to be
valid according to the intent. In the unsimplified code, before executing
the "return a->y" you have for read access to "*a_y":

object 1: address X, sizeof(int), effective type: int

for the return access you have

object 1: lvalue T1, address X, sizeof(T1), effective type: T1
object 2: lvalue int, address X, sizeof(int), effective type: int

The effective type in the access to object 1 was taken from the type of the
"lvalue" used for the access. For object 2, the effective type was used that
were set by the write in "*a_y = 2". Now for your simplification, before
executing the "return ((T1*)p)->y" you have for the preceeding write:

object 1: address X, sizeof(T2), effective type: T2
object 2: address X, sizeof(int), affective type: int

Now you are doing a member access in the return statement accessing the
first object using an "lvalue" of type T1 but the object has effective type
T1, violating the aliasing rule.

Joshua Maurice

unread,
Feb 6, 2011, 6:03:24 PM2/6/11
to
On Feb 6, 2:36 pm, "Johannes Schaub (litb)"

I think that you need to look at it again. "a_y" originally held the
result of "& a->y ", but I slowly transformed it to hold the result of
"& b->y ".

The longer version of that one-step simplification is:


T1* a = (T1*) p;
T2* b = (T2*) p;
int* a_y = & b->y;
int* b_y = & b->y;
*a_y = 2;
return a->y;

simplifies to:


T1* a = (T1*) p;
T2* b = (T2*) p;

int* a_y = & b->y;


*a_y = 2;
return a->y;

simplifies to:
int* a_y = & ((T2*)p)->y;
*a_y = 2;
return ((T1*)p)->y;
simplifies to:

Johannes Schaub (litb)

unread,
Feb 6, 2011, 6:34:37 PM2/6/11
to
Joshua Maurice wrote:

I think "& a->y" and "& b->y" are exactly equivalent.

> The longer version of that one-step simplification is:
> T1* a = (T1*) p;
> T2* b = (T2*) p;
> int* a_y = & b->y;
> int* b_y = & b->y;
> *a_y = 2;
> return a->y;
> simplifies to:
> T1* a = (T1*) p;
> T2* b = (T2*) p;
> int* a_y = & b->y;
> *a_y = 2;
> return a->y;
> simplifies to:
> int* a_y = & ((T2*)p)->y;
> *a_y = 2;
> return ((T1*)p)->y;
> simplifies to:
> ((T2*)p)->y = 2;
> return ((T1*)p)->y;

The last simplification in this longer version is invalid, I think. Prior to
the "return", in the second last version, you access one object and that
object only by an lvalue of type "int". In the second version prior to the
return (where you do a write access), you access two objects, the first of
which with an lvalue of type "T2" (and changes the effective type to that)
and the second of which by an lvalue of type int.

Ben Bacarisse

unread,
Feb 6, 2011, 6:54:54 PM2/6/11
to
"Johannes Schaub (litb)" <schaub....@googlemail.com> writes:

> Joshua Maurice wrote:
<snip>


>> #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* p = malloc(sizeof(T1));
>> /* ... */
>> }

<snip (but I think I've kept the part that matters for my comment)>

>> ((T2*)p)->y = 2;
>> return ((T1*)p)->y;

<snip>

> for the return access you have
>
> object 1: lvalue T1, address X, sizeof(T1), effective type: T1
> object 2: lvalue int, address X, sizeof(int), effective type: int
>
> The effective type in the access to object 1 was taken from the type of the
> "lvalue" used for the access.

I can't see any lvalue of type T1 in the return expression. The whole
malloced object never gets an effective type as far as I can see. I
note the "scare quotes" so maybe you have some slightly different
meaning for lvalue here.

There are only two things here that are lvalue expressions: 'p' and
'((T1*)p)->y'. One has type void * and the other has type int. Only
this second lvalue expression is used to access the object in question
(access to the pointer object 'p' is not at issue).

Just to clarify, a cast expression is not a lvalue and even if it were
the type of (T1 *)p is T1 * not T1. Also, in C, E->M is not defined to
be the same as (*E).M or there would certainly be an access via an
lvalue expression of type T1.

<snip>
--
Ben.

Wojtek Lerch

unread,
Feb 6, 2011, 11:17:28 PM2/6/11
to
On 06/02/2011 6:54 PM, Ben Bacarisse wrote:
> Also, in C, E->M is not defined to
> be the same as (*E).M or there would certainly be an access via an
> lvalue expression of type T1.

Why? Are you saying that whenever an lvalue expression such as S.M is
evaluated, it counts not only as an access to the member but also an
access to the whole structure? (Except, I assume, in a context where it
does not access an object at all, such as in &S.M?)

Or do you have something more subtle in mind, maybe along the lines that
the expression S.M accesses only the member, but it accesses it "via" an
lvalue expression of the structure type, without accessing the whole
structure, because the struct lvalue is a subexpression of the lvalue
designating the object actualy accessed?

Joshua Maurice

unread,
Feb 7, 2011, 12:22:44 AM2/7/11
to

Specifically, with regards to POSIX pthreads race conditions, and the
volatile rules, is there a difference between
* a->x = 1;
and
(*a).x = 1;
?

That would be kind of funny if there was a difference, where one would
cause more volatile reads or writes than the other, or where one would
could have a race condition but the other could not.

Joshua Maurice

unread,
Feb 7, 2011, 1:16:29 AM2/7/11
to
On Feb 6, 3:34 pm, "Johannes Schaub (litb)"

To be clear, you think that there's a difference between
a->x = 2;
and
int* x = & a->x;
*x = 2;
?

It would take me a long time to buy that.

Tim Rentsch

unread,
Feb 7, 2011, 6:10:24 AM2/7/11
to
"Johannes Schaub (litb)" <schaub....@googlemail.com> writes:

>[snip]


>
> In particular, I think the committee intends the spec to say that a struct
> or union access expression involves an access with the struct or union
> lvalue.
>
> T1 *p = malloc(sizeof *p);
> p->x = 0;
>
> In this case, I think the committee's intent is that the object pointed to
> by "p" is accesse by an lvalue of type T1, and so the effective type of the
> object containing the int changes to T1. So a later cast and access by an
> lvalue of T2 will be undefined behavior.

I'm not aware of any evidence that supports this theory (ie,
that using '.' or '->' is also an access for the left operand).
Furthermore it seems to be in conflict with the definitions the
Standard gives for access, value, etc.

Do you have any such evidence to offer? Or are you simply
stating an unsupported opinion?

Tim Rentsch

unread,
Feb 7, 2011, 6:39:34 AM2/7/11
to
Joshua Maurice <joshua...@gmail.com> writes:

> On Feb 6, 8:55 am, "Johannes Schaub (litb)"

>> [snip]


>>
>> In particular, I think the committee intends the spec to say that a struct
>> or union access expression involves an access with the struct or union
>> lvalue.
>>
>>     T1 *p = malloc(sizeof *p);
>>     p->x = 0;
>>
>> In this case, I think the committee's intent is that the object pointed to
>> by "p" is accesse by an lvalue of type T1, and so the effective type of the
>> object containing the int changes to T1. So a later cast and access by an
>> lvalue of T2 will be undefined behavior.
>
> I think this is also the only sensible interpretation of the

> committee's intent. [snip elaboration]

What I think you're trying to say is that this interpretation is
the only one that makes sense, and therefore must be what the
committee intended. (We don't know what the committee intended,
so there is no way to judge whether a particular interpretation
is the only sensible one, or indeed whether there is _any_
sensible meaning for what they intended.)

Regardless of what the committee might or might not have
intended, there certainly are alternative ways of reading
the standard that make as much sense as this one.

Tim Rentsch

unread,
Feb 7, 2011, 7:01:06 AM2/7/11
to
Ben Bacarisse <ben.u...@bsb.me.uk> writes:

> [snip]


> Also, in C, E->M is not defined to be the same as (*E).M

It's true that they aren't defined to be the same (and as you
point out a cast expression is not an lvalue), but there is a
sequence of equivalences (using '===' to mean "equivalent"):

(&E)->MOS === E.MOS // by footnote 83
(&(*P))->MOS === (*P).MOS // substituting (*P) for E
P->MOS === (*P).MOS // 6.5.3.2p3

Ben Bacarisse

unread,
Feb 7, 2011, 9:29:35 AM2/7/11
to
Wojtek Lerch <wojt...@yahoo.ca> writes:

> On 06/02/2011 6:54 PM, Ben Bacarisse wrote:
>> Also, in C, E->M is not defined to
>> be the same as (*E).M or there would certainly be an access via an
>> lvalue expression of type T1.
>
> Why? Are you saying that whenever an lvalue expression such as S.M is
> evaluated, it counts not only as an access to the member but also an
> access to the whole structure? (Except, I assume, in a context where
> it does not access an object at all, such as in &S.M?)

Hmm... I did not want to derail the discussion of the pointer access
but I think you are right. I should just have said "there would
certainly be an lvalue expression of type T1" without saying if there is
an access "via" it or not.

> Or do you have something more subtle in mind, maybe along the lines
> that the expression S.M accesses only the member, but it accesses it
> "via" an lvalue expression of the structure type, without accessing
> the whole structure, because the struct lvalue is a subexpression of
> the lvalue designating the object actualy accessed?

I think used to but I am really not sure anymore! One certainly can't
read the definition of . as implying an access of the whole structure,
but then I am puzzled by the pupose of the second to last of the access
rules (6.5 p7). I see that the rules don't use the term "via" but
simply "by". I think it might help if I start a new thread asking a
naive question about 6.5 p7.

--
Ben.

Ben Bacarisse

unread,
Feb 7, 2011, 9:40:12 AM2/7/11
to
I've honoured your followup-to header but I can't see why c.l.c readers
would not want to follow this.

Tim Rentsch <t...@alumni.caltech.edu> writes:

But, crucially, "the result is not a lvalue" and that matters when
talking about the effective type rules and the accesses they permit.

--
Ben.

Johannes Schaub (litb)

unread,
Feb 7, 2011, 11:09:46 AM2/7/11
to
Joshua Maurice wrote:

> On Feb 6, 3:34 pm, "Johannes Schaub (litb)"
> <schaub.johan...@googlemail.com> wrote:

>> [snipped]


> To be clear, you think that there's a difference between
> a->x = 2;
> and
> int* x = & a->x;
> *x = 2;
> ?
>
> It would take me a long time to buy that.

Yes I think there is a difference between te two. The first uses the struct
for the access. The second does not.

Tim Rentsch

unread,
Feb 7, 2011, 1:09:35 PM2/7/11
to
Ben Bacarisse <ben.u...@bsb.me.uk> writes:

Yes, that was sort of my point. Because the two forms are
supposed to be operationally equivalent but they are not
equivalent as regards lvalue-ness, trying to squeeze the
analysis into the framework of effective type rules is
suspect.

Tim Rentsch

unread,
Feb 7, 2011, 1:21:49 PM2/7/11
to
Ben Bacarisse <ben.u...@bsb.me.uk> writes:

> Wojtek Lerch <wojt...@yahoo.ca> writes:
[snip]


>> Or do you have something more subtle in mind, maybe along the lines
>> that the expression S.M accesses only the member, but it accesses it
>> "via" an lvalue expression of the structure type, without accessing
>> the whole structure, because the struct lvalue is a subexpression of
>> the lvalue designating the object actualy accessed?
>
> I think used to but I am really not sure anymore! One certainly can't
> read the definition of . as implying an access of the whole structure,
> but then I am puzzled by the pupose of the second to last of the access

> rules (6.5 p7). [snip]

To allow code where member objects are stored into directly, then
the containing structure or union is accessed as a whole, e.g.,

typedef struct { int x; } Foo;
...
int *p = malloc( sizeof (Foo) );
*p = 4;
return * (Foo*)p;

Wojtek Lerch

unread,
Feb 7, 2011, 1:27:45 PM2/7/11
to
On 07/02/2011 9:40 AM, Ben Bacarisse wrote:
> Tim Rentsch<t...@alumni.caltech.edu> writes:
>> Ben Bacarisse<ben.u...@bsb.me.uk> writes:
>>
>>> [snip]
>>> Also, in C, E->M is not defined to be the same as (*E).M
>>
>> It's true that they aren't defined to be the same (and as you
>> point out a cast expression is not an lvalue), but there is a
>> sequence of equivalences (using '===' to mean "equivalent"):
>>
>> (&E)->MOS === E.MOS // by footnote 83
>> (&(*P))->MOS === (*P).MOS // substituting (*P) for E
>> P->MOS === (*P).MOS // 6.5.3.2p3
>
> But, crucially, "the result is not a lvalue" and that matters when
> talking about the effective type rules and the accesses they permit.

Are you referring to 6.5.3.2p3 talking about &*P not being an lvalue?
Why would that matter to the effective type of what it points to?

Joshua Maurice

unread,
Feb 7, 2011, 3:11:18 PM2/7/11
to
On Feb 7, 8:09 am, "Johannes Schaub (litb)"

<schaub.johan...@googlemail.com> wrote:
> Joshua Maurice wrote:
> > On Feb 6, 3:34 pm, "Johannes Schaub (litb)"
> > <schaub.johan...@googlemail.com> wrote:
> >> [snipped]
> > To be clear, you think that there's a difference between
> >   a->x = 2;
> > and
> >   int* x = & a->x;
> >   *x = 2;
> > ?
>
> > It would take me a long time to buy that.
>
> Yes I think there is a difference between te two. The first uses the struct
> for the access. The second does not.

I never really considered this beyond a first glance.

Again, to be crystal clear, consider:
/* 1 */
a -> x = 2;
and
/* 2 */
* ( & ( a -> x )) = 2;
and
/* 3 */


int* x = & a->x;
*x = 2;

You really think there's a difference? Really? Where's the difference?
Between 1 and 2, or 2 and 3? I /hope/ between 1 and 2. 2 and 3 better
be entirely equivalent, or I'm really losing it.

As a naive understanding for the difference between 1 and 2: The
addressof operator (&) simply returns the address of the object
referred to the lvalue, and then the dereference operator (*) simply
takes that pointer value and returns back the same lvalue (which
refers to the same object). This isn't operator overloading in C++. I
would think that it ought to be a noop. If there is any difference at
all between any of 1, 2, and 3 above in this post, then I have a
fundamental misunderstanding of the language.

Wojtek Lerch

unread,
Feb 7, 2011, 4:31:43 PM2/7/11
to
On 07/02/2011 3:11 PM, Joshua Maurice wrote:
> Again, to be crystal clear, consider:
> /* 1 */
> a -> x = 2;
> and
> /* 2 */
> * (& ( a -> x )) = 2;
> and
> /* 3 */
> int* x =& a->x;

> *x = 2;
>
> You really think there's a difference? Really? Where's the difference?
> Between 1 and 2, or 2 and 3? I /hope/ between 1 and 2. 2 and 3 better
> be entirely equivalent, or I'm really losing it.
>
> As a naive understanding for the difference between 1 and 2: The
> addressof operator (&) simply returns the address of the object
> referred to the lvalue, and then the dereference operator (*) simply
> takes that pointer value and returns back the same lvalue (which
> refers to the same object). This isn't operator overloading in C++. I
> would think that it ought to be a noop. If there is any difference at
> all between any of 1, 2, and 3 above in this post, then I have a
> fundamental misunderstanding of the language.

I don't think a C pointer is simply just the address of an object. If
you consider the rules of pointer arithmetic and DR260, a pointer value
carries some extra properties that decide what operations on it are
defined. Two pointers may compare equal and be represented by identical
bit patterns, but depending on their "provenance", one of them may be
safe to dereference or increment but not decrement, while the other may
be safe to decrement but not increment or dereference. The standard
tells us that every object can be considered an array element, and every
pointer to an object has a range of integers that can be legitimately
added to it, based on the object's "arrayness"; but the standard rarely
bothers explaining how to determine what that array is, and all we can
do is rely on obvious guesses where they're obvious, and in less-obvious
cases we can hope that the guess is even harder for a compiler, forcing
it to generate code that does the "naive" thing regardless of what the
limit would be if the standard didn't neglect to specify it.

Since this "arrayness" is not exactly on topic here, I don't want to go
too deep into it now; but maybe pointers are also supposed to remember
their "structness", and your &a->x (and also your x) are not just
pointers to an int that is known not to be an array element, but
pointers to an int that is known not to be an array element but is also
known to be the "x" member of a struct T1? If that were the case, then
maybe a simple assignment to *x could still impose an effective type of
struct T1 on the object surrounding the int that x points to. But of
course none of that is actually discussed in the standard, just like the
transformations of "arrayness" are not discussed for most of the
operations where they apparently happen.

Joshua Maurice

unread,
Feb 7, 2011, 4:36:40 PM2/7/11
to

Indeed. This is exactly what I meant when I was saying "data
dependency analysis". I think your way is clearer. (It could be that)
pointer values carry with them some semantic information, in this case
it remembers that it came from a memberof expression on a T1 lvalue.
I'll have to check out that DR. Are there any other spots in the C
standard which you suggest that I look at regarding this "arrayness"?

Johannes Schaub (litb)

unread,
Feb 7, 2011, 9:07:43 PM2/7/11
to

The committee argues that way in the union DR. See http://www.open-
std.org/jtc1/sc22/wg14/www/docs/dr_236.htm .

It is loading more messages.
0 new messages