H
Hallvard B Furuseth
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);
}
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);
}