From Bugzilla Helper: User-Agent: Mozilla/5.0 (X11; U; Linux 2.2.19-7.0.1 i686; en-US; rv:0.9+) Gecko/20010508 Description of problem: g++ -O2 for 80x86 produces incorrect assembly code for the 'Cflatten' function in the following snippet (narrowed down and converted to valid C from a large C++ program): #include <stdio.h> typedef short int16; typedef long int32; typedef struct { int16 v, h; } Point; int32 Cflatten(Point *pp) { Point p; p.v = pp->v; p.h = pp->h; return (*((int32*))); } int main() { Point p = { 0x1234, 0x5678 }; int32 f; f = Cflatten(); printf("%08x\n", f); /* should print 56781234 on a little-endian machine */ } The Cflatten function generates the following x86 assembly: Cflatten__FP5Point: pushl %ebp movl %esp, %ebp pushl %eax movl 8(%ebp), %eax movw (%eax), %ax movw %ax, -4(%ebp) movl -4(%ebp), %eax leave ret which leaves the upper half of %eax uninitialized. How reproducible: Always Steps to Reproduce: 1.Compile the above code fragment with g++ -O2 2.Run and observe the output is bfff1234 instead of 56781234 3.Re-compile without -O2 and observe the correct output. Actual Results: Output is "bfff1234" Expected Results: Output should be "56781234" (on a little-endian machine) Additional info: The bug does not occur if you turn off the strict-aliasing optimization, e.g. "g++ -O2 -fno-strict-aliasing". It also does not occur if you use gcc instead of g++ to compile. If you change the initialization of Point p to use struct initialization syntax, or make p volatile, the problem goes away. Reading specs from /usr/lib/gcc-lib/i386-redhat-linux/2.96/specs gcc version 2.96 20000731 (Red Hat Linux 7.1 2.96-81) I haven't tried to see if this bug exists in a gcc 3.0 pre-release.
See bug #36862.
Bug 36862 referenced above is clearly related, yet this bug is closed as "not a bug". But there clearly IS a problem here. In my case, the entire 32-bit word should be read and returned, but it's returning half-uninitialized memory.
Apart from these two typos in your example, -return (*((int32*))); +return (*((int32*)&p)); -f = Cflatten(); +f = Cflatten(&p); and gcc -O2 returning 0x0000 in the upper 16 bits, the *((int32*)&p) cast is bad code and should be fixed. For an introduction to the problem, see "info gcc", node "Optimize Options", on -fstrict-aliasing, or search the web on type-punning.
Apologies for the 'typos'. I actually cut-and-pasted that into the web form from working code, but apparently the ampersands got eaten and I didn't notice. The results of web-searches on -fstrict-aliasing and type-punning consisted mostly of people complaining that code didn't work with gcc -fstrict-aliasing and being told by people from the gcc team that the code was bad and needed to be fixed. There was very little information on why it is considered valid for the compiler to make this optimization. There were references to the ANSI C Standard ISO-9899:1990 Sections 3.3 and 3.5.2.1, but the actual standard is impossible to find. Books containing it are out of print, and ansi.org appears to only have the 1999 standard available. If someone could e-mail me or append here just the relevant sections of the standard, or point me where I could find them, I'd appreciate it. I realize that code of the sort *(int32*)&p is inherently non-portable, but on a known architecture where the size of the pointers are the known to be the same, I find it shocking that compiler-writers would allow code like this to break without so much as a warning message. While not "clean", it is sometimes necessary. For example, some of our code which does this kind of thing is reading an arbitrary block of data over a TCP connection, and interpreting the content by dynamically parsing a type-descriptor at run-time. If deferencing pointer casts does not work, how else is one supposed to do this? And even if declared technically legal by the standard, it seems like turning on an optimization by default in -O2 with the potential to break lots of code is a bad idea. And I'm sure I'm not alone. Here's a nice quote on the subject from Linux Torvalds (Linux Kernel mailing list, Dec 2000): > I hope that's another thing that the gcc people fix by the time they do a > _real_ release. Anobody who thinks that "-fstrict-aliasing" being on by > default is a good idea is probably a compiler person who hasn't seen real > code.
> I realize that code of the sort *(int32*)&p is inherently > non-portable, but on a known architecture where the size > of the pointers are the known to be the same, Not the size of the pointers is the problem, but finding out when to update the object pointed to in memory, i.e. whether the value may be cached until it is accessed the next time. Checking out the C9X Rational or the C9X Committee Draft might be an idea. IIRC, it is somewhere in section 6.5. I've copied this from my Sent folder. I think it's an excerpt from the C9X Rational: In principle, then, aliasing only need be allowed for when the lvalues all have the same type. In practice, the C89 Committee recognized certain prevalent exceptions: * The lvalue types may differ in signedness. In the common range, a signed integer type and its unsigned variant have the same representation; and it was felt that an appreciable body of existing code is not "strictly typed" in this area. * Character pointer types are often used in the bytewise manipulation of objects; a byte stored through such a character pointer may well end up in an object of any type. * A qualified version of the object's type, though formally a different type, provides the same interpretation of the value of the object.
Thanks Michael for explaining instead of myself. You basically have 3 options: either decide you don't want to fix your code, but then you should stick -fno-strict-aliasing into your Makefiles. Or make sure one of the types is a char (whatever signedness) pointer, ie. you can cast a pointer to anything to a char pointer, or a char pointer to pointer to whatever and access through that (note that the problem is not the casting itself, but accessing the same memory location with two incompatible types). The third option is to use unions, e.g. above you could either int32 Cflatten(Point *pp) { Point p; p.v = pp->v; p.h = pp->h; return ((union { Point p; int32 i; } *)&p)->i; } or: int32 Cflatten(Point *pp) { union { Point p; int 32 i; } p; p.p.v = pp->v; p.p.h = pp->h; return p.i; }
Well, I'm partially mollified by the exception for 'char', which would make most code which does this sort of thing continue to work correctly. I still agree with Linus that it was a bad idea to turn on this optimization bu default in -O2. The problem is that there is no way to tell when the code does not compile as expected. I would prefer to fix our source code over using -fno-strict-aliasing, but I don't know how to go about finding all such code in a deterministic way. Would you consider adding an option to gcc to generate a warning when code is encountered that due to aliasing might have a different effect than the user would expect?
Upon review of our valid but aging backlog the Satellite Team has concluded that this Bugzilla does not meet the criteria for a resolution in the near term, and are planning to close in a month. This message may be a repeat of a previous update and the bug is again being considered to be closed. If you have any concerns about this, please contact your Red Hat Account team. Thank you.