473,321 Members | 1,708 Online
Bytes | Software Development & Data Engineering Community
Post Job

Home Posts Topics Members FAQ

Join Bytes to post your question to a community of 473,321 software developers and data experts.

gcc, aliasing rules and unions

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
3 4757
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
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
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 thread has been closed and replies have been disabled. Please start a new discussion.

Similar topics

4
by: Yuri Victorovich | last post by:
In short my question is: If I overload "operator new" for class A and return from it instance of struct B (unrelated with A) as allocated memory area for A should aliasing rules work and allow...
2
by: Bryan Parkoff | last post by:
I create one 32 Bits variable and four pointer variables. Four pointer variables link or point to one 32 Bits variable. Each pointer variable is 8 Bits. Look at my example below. unsigned int...
9
by: Adam Warner | last post by:
Hi all, Message ID <c1qo3f0tro@enews2.newsguy.com> is one of many informative articles by Chris Torek about C. The particular message discusses aliasing and concludes with this paragraph: ...
20
by: nicolas.riesch | last post by:
I try to understand strict aliasing rules that are in the C Standard. As gcc applies these rules by default, I just want to be sure to understand fully this issue. For questions (1), (2) and...
9
by: liljencrantz | last post by:
Hi, I have a piece of code that uses hashtables to store pointers to various bits of data. The hashtable sees all pointers as const void *, while the application obviously uses various other...
10
by: Old Wolf | last post by:
Consider the following program: #include <stdio.h> int main(void) { /* using malloc to eliminate alignment worries */ unsigned long *p = malloc( sizeof *p ); if ( p && sizeof(long) ==...
3
by: David Mathog | last post by:
I have a program for which this line: if(! lstrtol(&atoken,length-2,(long *) &(lclparams->pad)) || (lclparams->pad< 0)){ generates the warning below, but ONLY if the gcc compiler is at -O2 or...
3
by: Squat'n Dive | last post by:
Does anyone have an idea why -fno-strict-aliasing is turned off when cross compiling? in configure generated for 2.4.4: case $GCC in yes) # Python violates C99 rules, by casting between...
4
by: Paul Brettschneider | last post by:
Hello all, consider the following code: typedef char T; class test { T *data; public: void f(T, T, T); void f2(T, T, T);
0
by: DolphinDB | last post by:
Tired of spending countless mintues downsampling your data? Look no further! In this article, you’ll learn how to efficiently downsample 6.48 billion high-frequency records to 61 million...
0
by: ryjfgjl | last post by:
ExcelToDatabase: batch import excel into database automatically...
1
isladogs
by: isladogs | last post by:
The next Access Europe meeting will be on Wednesday 6 Mar 2024 starting at 18:00 UK time (6PM UTC) and finishing at about 19:15 (7.15PM). In this month's session, we are pleased to welcome back...
0
by: jfyes | last post by:
As a hardware engineer, after seeing that CEIWEI recently released a new tool for Modbus RTU Over TCP/UDP filtering and monitoring, I actively went to its official website to take a look. It turned...
0
by: ArrayDB | last post by:
The error message I've encountered is; ERROR:root:Error generating model response: exception: access violation writing 0x0000000000005140, which seems to be indicative of an access violation...
1
by: PapaRatzi | last post by:
Hello, I am teaching myself MS Access forms design and Visual Basic. I've created a table to capture a list of Top 30 singles and forms to capture new entries. The final step is a form (unbound)...
1
by: Defcon1945 | last post by:
I'm trying to learn Python using Pycharm but import shutil doesn't work
0
by: af34tf | last post by:
Hi Guys, I have a domain whose name is BytesLimited.com, and I want to sell it. Does anyone know about platforms that allow me to list my domain in auction for free. Thank you
0
by: Faith0G | last post by:
I am starting a new it consulting business and it's been a while since I setup a new website. Is wordpress still the best web based software for hosting a 5 page website? The webpages will be...

By using Bytes.com and it's services, you agree to our Privacy Policy and Terms of Use.

To disable or enable advertisements and analytics tracking please visit the manage ads & tracking page.