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()); }
Fix: The same code should be generated. Or see the other 'RemoveOne' implementation. David Schwartz <davids@webmaster.com>
newhead=*(void **) head; *(char **) head=NULL; You are violating aliasing rules here. Either fix your code or use -fno-strict-aliasing.
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
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".
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.
(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
Is this code legal without -fno-strict-aliasing? void *foo=malloc(sizeof(void *)); *(void **)foo=malloc(16); DS
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.
newhead=*(void **) head; *(char **) head=NULL; is undefined because of what I mentioned, nothing more than that.
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
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.
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.
Reopening to ...
Mark as a dup of bug 21920. *** This bug has been marked as a duplicate of 21920 ***