Question

Test the following code:

#include <stdio.h>
#include <stdlib.h>
main()
{
    const char *yytext="0";
    const float f=(float)atof(yytext);
    size_t t = *((size_t*)&f);
    printf("t should be 0 but is %d\n", t);
}

Compile it with:

gcc -O3 test.c

The GOOD output should be:

"t should be 0 but is 0"

But with my gcc 4.1.3, I have:

"t should be 0 but is -1209357172"
Was it helpful?

Solution

Use the compiler flag -fno-strict-aliasing.

With strict aliasing enabled, as it is by default for at least -O3, in the line:

size_t t = *((size_t*)&f);

the compiler assumes that the size_t* does NOT point to the same memory area as the float*. As far as I know, this is standards-compliant behaviour (adherence with strict aliasing rules in the ANSI standard start around gcc-4, as Thomas Kammeyer pointed out).

If I recall correctly, you can use an intermediate cast to char* to get around this. (compiler assumes char* can alias anything)

In other words, try this (can't test it myself right now but I think it will work):

size_t t = *((size_t*)(char*)&f);

OTHER TIPS

In the C99 standard, this is covered by the following rule in 6.5-7:

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,

  • a qualified version of a type compatible with the effective type of the object,

  • a type that is the signed or unsigned type corresponding to the effective type of the object,

  • a type that is the signed or unsigned type corresponding to a qualified version of 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), or

  • a character type.

The last item is why casting first to a (char*) works.

This is no longer allowed according to C99 rules on pointer aliasing. Pointers of two different types cannot point to the same location in memory. The exceptions to this rule are void and char pointers.

So in your code where you are casting to a pointer of size_t, the compiler can choose to ignore this. If you want to get the float value as a size_t, just assign it and the float will be cast (truncated not rounded) as such:

size_t size = (size_t)(f); // this works

This is commonly reported as a bug, but in fact really is a feature that allows optimizers to work more efficiently.

In gcc you can disable this with a compiler switch. I beleive -fno_strict_aliasing.

It is bad C code :-)

The problematic part is that you access one object of type float by casting it to an integer pointer and dereferencing it.

This breaks the aliasing rule. The compiler is free to assume that pointers to different types such as float or int don't overlap in memory. You've done exactly that.

What the compiler sees is that you calculate something, store it in the float f and never access it anymore. Most likely the compiler has removed part of the code and the assignment has never happend.

The dereferencing via your size_t pointer will in this case return some uninitialized garbage from the stack.

You can do two things to work-around this:

  1. use a union with a float and a size_t member and do the casting via type punning. Not nice but works.

  2. use memcopy to copy the contents of f into your size_t. The compiler is smart enough to detect and optimize this case.

Why would you think that t should be 0?

Or, more accuractely phrased, "Why would you think that the binary representation of a floating point zero would be the same as the binary representation of an integer zero?"

This is bad C code. Your cast breaks C aliasing rules, and the optimiser is free do things that break this code. You will probably find that GCC has cheduled the size_t read before the floating-point write (to hide fp pipeline latency).

You can set the -fno-strict-aliasing switch, or use a union or a reinterpret_cast to reinterpret the value in a standards-compliant way.

Aside the pointer alignments, you're expecting that sizeof(size_t)==sizeof(float). I don't think it is (on 64-bit Linux size_t should be 64 bits but float 32 bits), meaning your code will read something uninitialized.

-O3 is not deemed "sane", -O2 is generally the upper threshold except maybe for some multimedia apps.

Some apps can't even go that far, and die if you go beyond -O1 .

If you have a new enough GCC ( I'm on 4.3 here ), it may support this command

  gcc -c -Q -O3 --help=optimizers > /tmp/O3-opts

If you're careful, you'll possibly be able to go through that list and find the given singular optimization you're enabling which causes this bug.

From man gcc :

  The output is sensitive to the effects of previous command line options, so for example it is possible to find out which
       optimizations are enabled at -O2 by using:

               -O2 --help=optimizers

       Alternatively you can discover which binary optimizations are enabled by -O3 by using:

               gcc -c -Q -O3 --help=optimizers > /tmp/O3-opts
               gcc -c -Q -O2 --help=optimizers > /tmp/O2-opts
               diff /tmp/O2-opts /tmp/O3-opts | grep enabled

I tested your code with: "i686-apple-darwin9-gcc-4.0.1 (GCC) 4.0.1 (Apple Inc. build 5465)"

and there was no Problem. Output:

t should be 0 but is 0

So there isn't a bug in you code. That doesn't mean that it is good code. But I would add the returntype of the main-function and the "return 0;" at the end of the function.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top