By using this site, you agree to our updated Privacy Policy and our Terms of Use. Manage your Cookies Settings.
424,836 Members | 2,059 Online
Bytes IT Community
+ Ask a Question
Need help? Post your question and get tips & solutions from a community of 424,836 IT Pros & Developers. It's quick & easy.

gcc, aliasing rules and unions

P: n/a
I'm getting horribly lost in the strict aliasing rules.
Is this code correct?

struct A { int x; };
struct B { int x, y; };

int foo( struct A *a ) {
struct B *b = (struct B *) a;
return b->x - b->y;
}

int bar(int x, int y)
{
struct B b1;
b1.x = x;
b1.y = y;
return foo( (struct A *) &b1 );
}

int baz(int x, int y)
{
union {
struct A a2;
struct B b2;
} u;
u.b2.x = x;
u.b2.y = y;
return foo( &u.a2 );
}

bar() seems correct. A struct pointer can be converted to another
struct pointer type and back again (C99 6.2.5p26 and 6.3.2.3p7), and
foo() accesses the b1 object through its effective type (the type it was
created/stored with, 6.5p6-p7).

But is baz() correct? gcc -O2 -Wall warns about bar(): "dereferencing
type-punned pointer will break strict-aliasing rules". The gcc manual
seems to suggest it should be converted to baz(), though its example is
about code which is non-standard either way. But I can't quite find
anything in the standard which says baz() is OK. It was all so simple
before the strict aliasing rules...
Maybe &u.a2 works like (struct A *)&u.b2 so that it can be cast "back"
to struct B*? It has the same address, but I can't find where that
helps for the aliasing rules. But if so, baz() is fine.

It would be OK for baz() but not for foo() to access u.a2.x, according
to 6.5.2.3p5 and 6.5.2.3p8. But that's not what this code does. Baz()
uses a2 itself, not a2.x - and it doesn't access it, it takes it
address.

Also I don't know what the "effective type" of a union member is. I
_thought_ it was the type of the last stored value, but I can't find
that in the Standard. 6.5p6 applies to objects with "no declared type",
which does not seem to fit union members.
Here are the texts I used to confuse myself with:

Gcc manual, in Info node Optimize Options or
<http://gcc.gnu.org/onlinedocs/gcc-4.0.2/gcc/Optimize-Options.html>:

-fstrict-aliasing

Allows the compiler to assume the strictest aliasing rules
applicable to the language being compiled. For C (and C++), this
activates optimizations based on the type of expressions. In
particular, an object of one type is assumed never to reside at the same
address as an object of a different type, unless the types are almost
the same. For example, an unsigned int can alias an int, but not a
void* or a double. A character type may alias any other type.

Pay special attention to code like this:

union a_union {
int i;
double d;
};
int f() {
a_union t;
t.d = 3.0;
return t.i;
}

The practice of reading from a different union member than the one
most recently written to (called "type-punning") is common. Even with
-fstrict-aliasing, type-punning is allowed, provided the memory is
accessed through the union type. So, the code above will work as
expected. However, this code might not:

int f() {
a_union t;
int* ip;
t.d = 3.0;
ip = &t.i;
return *ip;
}
C99 Standard:

6.2.5p26: All pointers to structure types shall have the same
representation and alignment requirements as each other.

6.3.2.3p7: A pointer to an object (...) may be converted to a pointer to
a different object (...). If the resulting pointer is not correctly
aligned[57] for the pointed-to type, the behavior is undefined.
Otherwise, when converted back again, the result shall compare equal
to the original pointer.

[39] The same representation and alignment requirements are meant to
imply interchangeability as arguments to functions, return values
from functions, and members of unions.

[57] In general, the concept "correctly aligned" is transitive: if a
pointer to type A is correctly aligned for a pointer to type B,
which in turn is correctly aligned for a pointer to type C, then a
pointer to type A is correctly aligned for a pointer to type C.

6.5p6: The effective type of an object for an access to its stored value
is the declared type of the object, if any.[72] 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. (...)
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.

[72] Allocated objects have no declared type.

6.5p7: An object shall have its stored value accessed only by an lvalue
expression that has one of the following types:[73]
- a type compatible with the effective type of the object,
(...)
- an aggregate or union type that includes one of the aforementioned
types among its members (including, recursively, a member of a
subaggregate or contained union),
(...)

[73] The intent of this list is to specify those circumstances in which
an object may or may not be aliased.

6.5.2.3p5: One special guarantee is made in order to simplify the use of
unions: if a union contains several structures that share a common
initial sequence (see below), and if the union object currently
contains one of these structures, it is permitted to inspect the
common initial part of any of them anywhere that a declaration of
the complete type of the union is visible.

6.5.2.3p8 EXAMPLE 3:
The following is not a valid fragment (because the union type is
not visible within function f):
struct t1 { int m; };
struct t2 { int m; };
int f(struct t1 * p1, struct t2 * p2)
{
if (p1->m < 0)
p2->m = -p2->m;
return p1->m;
}
int g()
{
union { struct t1 s1;
struct t2 s2;
} u;
/* ... */
return f(&u.s1, &u.s2);
}

--
Hallvard
Apr 18 '06 #1
Share this Question
Share on Google+
3 Replies


P: n/a
On 2006-04-18, Hallvard B Furuseth <h.**********@usit.uio.no> wrote:
I'm getting horribly lost in the strict aliasing rules.
Is this code correct?

struct A { int x; };
struct B { int x, y; };

int foo( struct A *a ) {
struct B *b = (struct B *) a;
return b->x - b->y;
}

int bar(int x, int y)
{
struct B b1;
b1.x = x;
b1.y = y;
return foo( (struct A *) &b1 );
}

int baz(int x, int y)
{
union {
struct A a2;
struct B b2;
} u;
u.b2.x = x;
u.b2.y = y;
return foo( &u.a2 );
}

bar() seems correct. A struct pointer can be converted to another
struct pointer type and back again (C99 6.2.5p26 and 6.3.2.3p7), and
foo() accesses the b1 object through its effective type (the type it was
created/stored with, 6.5p6-p7).

But is baz() correct? gcc -O2 -Wall warns about bar(): "dereferencing
type-punned pointer will break strict-aliasing rules". The gcc manual
seems to suggest it should be converted to baz(), though its example is
about code which is non-standard either way. But I can't quite find
anything in the standard which says baz() is OK. It was all so simple
before the strict aliasing rules...


As far as I know both are equally correct from the point of the view of
the standard. The idea of "assume no aliasing between pointers to
objects where the types of the objects are reasonably different" is a
gcc optimization. I don't know if the standard says anything about this
(e.g. whether it recommends this particular optimization).

Technically I think the compiler always has to expect aliasing between
pointers (unless you use restrict, I don't know if restrict is in any of
the standards), and gcc on high optimization levels may produce
incorrect code if you do pointer casts to access one object as an object
of an unrelated type. You might get the problem in code like this:

int f(struct B* b)
{
...
struct A* a = (struct A*) b;
b->x++;
return a->x;
}

gcc may have assumed that *a is not the same object as *b (because why
would anyone store a struct A in a location pointed to by a struct B
*?), and therefore have failed to recognize the data dependence between
the second and third lines. It may then have incorrectly reordered the
operations so that b->x++ happens after a->x is returned instead of
before.

People write code like this sometimes when they want to simulate something
like C++ inheritance in C. I think using unions for this kind of thing is
nicer style anyway, and works better with gcc.

It's better to fix the code so the warnings go away and leave the
optimizations turned on if you can.
Apr 18 '06 #2

P: n/a
Sorry to disappear like that...

Ben, Gcc makes the "assume no aliasing between pointers to objects where
the types of the objects are reasonably different" optimization because
the standard allows it. That's what paragraphs 6.5p7 and so on which I
quoted are all about. If the standard dit not allow it, 'gcc -O2' would
be very broken in doing this. So my original question remains:

Ben C writes:
On 2006-04-18, Hallvard B Furuseth <h.**********@usit.uio.no> wrote:
I'm getting horribly lost in the strict aliasing rules.
Is this code correct?

struct A { int x; };
struct B { int x, y; };

int foo( struct A *a ) {
struct B *b = (struct B *) a;
return b->x - b->y;
}

int bar(int x, int y)
{
struct B b1;
b1.x = x;
b1.y = y;
return foo( (struct A *) &b1 );
}

int baz(int x, int y)
{
union {
struct A a2;
struct B b2;
} u;
u.b2.x = x;
u.b2.y = y;
return foo( &u.a2 );
}

bar() seems correct. A struct pointer can be converted to another
struct pointer type and back again (C99 6.2.5p26 and 6.3.2.3p7), and
foo() accesses the b1 object through its effective type (the type it was
created/stored with, 6.5p6-p7).

But is baz() correct? gcc -O2 -Wall warns about bar(): "dereferencing
type-punned pointer will break strict-aliasing rules". The gcc manual
seems to suggest it should be converted to baz(), though its example is
about code which is non-standard either way. But I can't quite find
anything in the standard which says baz() is OK. It was all so simple
before the strict aliasing rules...
As far as I know both are equally correct from the point of the view of
the standard. The idea of "assume no aliasing between pointers to
objects where the types of the objects are reasonably different" is a
gcc optimization. I don't know if the standard says anything about this
(e.g. whether it recommends this particular optimization).


The standard allows it in some circumstances - and as I said, as far as
I can tell it allows in baz() and that makes baz() buggy, even though
gcc recommends baz() over bar().
Technically I think the compiler always has to expect aliasing between
pointers (unless you use restrict, I don't know if restrict is in any
of the standards), and gcc on high optimization levels may produce
incorrect code if you do pointer casts to access one object as an
object of an unrelated type.


No, as I said, the compiler may assume that objects of different types
are not aliased, with some exceptions.

--
Hallvard
May 2 '06 #3

P: n/a
On 2006-05-02, Hallvard B Furuseth <h.**********@usit.uio.no> wrote:
Sorry to disappear like that...

Ben, Gcc makes the "assume no aliasing between pointers to objects where
the types of the objects are reasonably different" optimization because
the standard allows it. That's what paragraphs 6.5p7 and so on which I
quoted are all about. If the standard dit not allow it, 'gcc -O2' would
be very broken in doing this. So my original question remains:
Ah sorry, yes I didn't read your original post properly.
Ben C writes:
On 2006-04-18, Hallvard B Furuseth <h.**********@usit.uio.no> wrote:
I'm getting horribly lost in the strict aliasing rules.
Is this code correct?

struct A { int x; };
struct B { int x, y; };

int foo( struct A *a ) {
struct B *b = (struct B *) a;
return b->x - b->y;
}

int bar(int x, int y)
{
struct B b1;
b1.x = x;
b1.y = y;
return foo( (struct A *) &b1 );
}

int baz(int x, int y)
{
union {
struct A a2;
struct B b2;
} u;
u.b2.x = x;
u.b2.y = y;
return foo( &u.a2 );
}


[snip]

As you said (I think) in your original post, baz() should be incorrect
inasmuch as it's similar to this:

6.5.2.3p8 EXAMPLE 3:
The following is not a valid fragment (because the union type is
not visible within function f):
struct t1 { int m; };
struct t2 { int m; };
int f(struct t1 * p1, struct t2 * p2)
{
if (p1->m < 0)
p2->m = -p2->m;
return p1->m;
}
int g()
{
union { struct t1 s1;
struct t2 s2;
} u;
/* ... */
return f(&u.s1, &u.s2);
}

I tried compiling the actual code in the example in 6.5.2.3p8, and gcc
didn't warn about that either.

It may be that gcc is not trying to do any aliasing optimization in this
case, and so that's why it doesn't warn.

After all, you don't get the warning about bar's call to foo either if
you compile on -O0. This was the example:

int foo( struct A *a ) {
struct B *b = (struct B *) a;
return b->x - b->y;
}

int bar(int x, int y)
{
struct B b1;
b1.x = x;
b1.y = y;
return foo( (struct A *) &b1 );
}

In other words, gcc isn't trying to be a sort of lint that tells you if
you're keeping to the standard or not, it just tries to tell when it
might be compiling something you didn't expect.

I think baz() is wrong and gcc doesn't care.
May 2 '06 #4

This discussion thread is closed

Replies have been disabled for this discussion.