Bug 17665 - wrong code with -O2
Summary: wrong code with -O2
Status: RESOLVED DUPLICATE of bug 21920
Alias: None
Product: gcc
Classification: Unclassified
Component: c (show other bugs)
Version: 3.4.2
: P1 normal
Target Milestone: ---
Assignee: Not yet assigned to anyone
URL:
Keywords:
Depends on:
Blocks:
 
Reported: 2004-09-24 23:13 UTC by davids
Modified: 2005-07-23 22:49 UTC (History)
1 user (show)

See Also:
Host: i686-pc-linux-gnu
Target: i686-pc-linux-gnu
Build: i686-pc-linux-gnu
Known to work:
Known to fail:
Last reconfirmed:


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description davids 2004-09-24 23:13:06 UTC

The wrong code is generated for -O2 on a dereference of a pointer to a 'void
*'.

Environment:
System: Linux youknow.youwant.to 2.4.20-28.7smp #1 SMP Thu Dec 18 11:18:31 EST 2003 i686 unknown
Architecture: i686

	
host: i686-pc-linux-gnu
build: i686-pc-linux-gnu
target: i686-pc-linux-gnu
configured with: ../configure --enable-languages=c++,java --enable-shared --enable-threads=posix --disable-checking --with-system-zlib --program-suffix=342

How-To-Repeat:

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

// Optimization issue with g++ 2.96 and 3.4.2 and probably others.
// With -O2, I get "3 1 0" and "3 1 2"
// With -O1, I get "3 1 2" and "3 1 2"

void *GetBlocks(void)
{
 void *block1=malloc(64);
 void *block2=malloc(64);
 void *block3=malloc(64);
 *(void **) block1=block2;
 *(void **) block2=block3;
 *(void **) block3=NULL;
 return block1;
}

int CountList(void *a)
{
 int ret=0;
 while(a!=NULL)
 {
  ret++;
  a=*(void **) a;
 }
 return ret;
}


void RemoveOneA(void *head)
{
 int l1, l2, l3;
 void *newhead;
 l1=CountList(head);
 newhead=*(void **) head;
 *(char **) head=NULL;
 l2=CountList(head);
 l3=CountList(newhead);
 printf("%d %d %d\n", l1, l2, l3);
}

void RemoveOneB(void *head)
{
 int l1, l2, l3;
 void *newhead;
 l1=CountList(head);
 newhead=*(char **) head;
 *(char **) head=0;
 l2=CountList(head);
 l3=CountList(newhead);
 printf("%d %d %d\n", l1, l2, l3);
}

int main(void)
{
 RemoveOneA(GetBlocks());
 RemoveOneB(GetBlocks());
}
Comment 1 davids 2004-09-24 23:13:06 UTC
Fix:

The same code should be generated. Or see the other 'RemoveOne'
implementation.

David Schwartz
<davids@webmaster.com>
Comment 2 Andrew Pinski 2004-09-24 23:39:14 UTC
 newhead=*(void **) head;
 *(char **) head=NULL;

You are violating aliasing rules here. Either fix your code or use -fno-strict-aliasing.
Comment 3 davids 2004-09-25 05:41:15 UTC
I am perfectly willing to entertain the possibility that I am being dense or 
misguided, but we're talking about 'void *' here. You can't get a 'void *' by 
taking the address of a 'void' and you can't dereference a 'void *' to get 
a 'void'. The only use of 'void *' is for type aliasing.

If you cannot alias at all without '-fno-strict-aliasing', that would imply 
that you can do *nothing* with a 'void *' without defining it. That, obviously, 
can't be right.

The documentation for '-fstrict-aliasing' talks about an object of one type 
being at the same address as an object of another type. But 'void' is not a 
type of object, so I don't see that I'm violating aliasing rules.

DS
Comment 4 Joseph S. Myers 2004-09-25 08:48:56 UTC
Subject: Re:  wrong code with -O2

On Sat, 25 Sep 2004, davids at webmaster dot com wrote:

> I am perfectly willing to entertain the possibility that I am being dense or 
> misguided, but we're talking about 'void *' here. You can't get a 'void *' by 
> taking the address of a 'void' and you can't dereference a 'void *' to get 
> a 'void'. The only use of 'void *' is for type aliasing.

Look at the code quoted.  You are using the same memory to store an object 
of type "void *" and an object of type "char *".  Although those objects 
have the same representation and alignment requirements, this is not 
permitted aliasing (whereas you could, for example, use the same memory to 
store "int" and "unsigned int").

> The documentation for '-fstrict-aliasing' talks about an object of one type 
> being at the same address as an object of another type. But 'void' is not a 
> type of object, so I don't see that I'm violating aliasing rules.

The problematic object types in your code are "void *" and "char *" 
(accessed by dereferencing "void **" and "char **" pointers), not "void" 
and "char".

Comment 5 Giovanni Bajo 2004-09-25 10:55:54 UTC
The solution is dereferencing the void**, and only then casting back to char*. 
Or at least I believe so, I haven't investigated your code that much.
Comment 6 davids 2004-09-25 17:35:18 UTC
(In reply to comment #4)

> The problematic object types in your code are "void *" and "char *" 
> (accessed by dereferencing "void **" and "char **" pointers), not "void" 
> and "char".

This is legal C and C++. The *only* use for 'void *' is to alias something. If 
it was 'char *' and 'int *', I'd 100% agree with you. However, you are 
specifically permitted to alias 'void *'.

Other than returning NULL or using it in a union, there is nothing you can do 
with 'void *' other than to use it to hold the same thing as some other type.

A pointer to a void is not actually a pointer to a void, it's a generic pointer 
type. Its typical use, defined by the standard, is to cast other types through 
it.

Unless you can explain how my use of 'void *' differs from the types 
specifically permitted by the standard, calling this bug report invalid is 
equivalent to saying you can't use 'void *' without defining -fno-strict-
aliasing.

Basically, what I'm saying is that because the standard specifically allows 
(and precisely defined the affects of) casting other pointer types to 'void *', 
the compiler should not assume that a 'void *' isn't an alias of another type, 
thought it is entirely justified in doing so for other types.

DS
Comment 7 davids 2004-09-25 17:56:41 UTC
Is this code legal without -fno-strict-aliasing?

void *foo=malloc(sizeof(void *));
*(void **)foo=malloc(16);

DS
Comment 8 Andrew Pinski 2004-09-25 18:09:06 UTC
No read the standard again
What it says is that only char can aliasing anything and nothing else.
since you access it via both char* and void* that is what violates the aliasing rules.
Comment 9 Andrew Pinski 2004-09-25 18:09:56 UTC
 newhead=*(void **) head;
 *(char **) head=NULL;
is undefined because of what I mentioned, nothing more than that.
Comment 10 davids 2004-09-25 18:24:37 UTC
Look in 'alias.c' and search for the string 'a void pointer'. The aliasing code 
is supposed to assume that a 'void *' can alias anything. The variable 'head' 
is a 'void *'.

I'll check the standard again, but my recollection is that it's supposed to be 
safe to cast any pointer to and from a 'void *'. That's why 'malloc' 
returns 'void *'.

DS
Comment 11 Joseph S. Myers 2004-09-25 18:30:22 UTC
Subject: Re:  wrong code with -O2

On Sat, 25 Sep 2004, davids at webmaster dot com wrote:

> I'll check the standard again, but my recollection is that it's supposed to be 
> safe to cast any pointer to and from a 'void *'. That's why 'malloc' 
> returns 'void *'.

The problematic dereference is not of a void * pointer, but of void ** and 
char ** pointers pointing to the same place.  You can use void * and 
double * pointers (for example) pointing to the same place (though 
dereferencing the void * pointer must involve casting it to char * or 
double *).  You can use int * and unsigned int * pointers pointing to the 
same place.  But you cannot use void ** and char ** pointers pointing to 
the same place.

The *value* of a void * pointer can safely be converted to another pointer 
type.  What you are doing is treating the *representation* of that pointer 
as being another type, via void ** and char ** pointers, and this is not 
safe.

Read 6.5#7 again.  Your object has one of void * and char * as effective 
type, but its value is being accessed as the other, which is not 
permitted: it is not within any of the listed cases.  You need to access 
the value as its effective type, then convert, rather than type punning 
the representation.

Comment 12 davids 2004-09-26 00:28:49 UTC
Ahh, I think I get it now. If I have:

void *foo;

Then 'foo' follows the aliasing rules for a 'void *'. But if I do:

(void **)foo

The fact that 'foo' is a 'void *' has no affect on aliasing because *I* casted 
it to a 'void **'. So it follows the aliasing rules for 'void **' because 
that's what I specifically asked it to do.

Thanks for your patience.
Comment 13 Andrew Pinski 2005-06-05 08:56:57 UTC
Reopening to ...
Comment 14 Andrew Pinski 2005-06-05 08:57:12 UTC
Mark as a dup of bug 21920.

*** This bug has been marked as a duplicate of 21920 ***