Sunday, July 11, 2010

Using c++ references for nicer code

It is common to see castings and conversions between different types of primitives in c++ code.
Many times however you don't want to do any conversion of the value, but instead just want to reference the binary representation of the variable as another type.

The common ugly way to do this is something like this:

// Program Entry Point
int main() {

// Make x hold the binary representation of a
// positive infinity float
int x = 0x7f800000;

// Treat x as if it was holding a float instead of an int,
// and then give f the same value
float f = *(float*)&x;

// Print float f (positive infinity) to the console
cout << f << endl;
return 0;
}


This does work, however as you can see the second line of code is pretty ugly.
The reason you can't just do "float f = (float)x;", is because that would mean you're casting the int value to a float; so the value is actually converted to a floating point value.

But that's not what we wanted, we instead want the 'int x' to behave as it was a float the entire time; that is why we first take the address of 'x' (&x), then treat it as a float pointer ((float*)&x), then finally dereference it back (*(float*)&x).

With c++ however, you can use references to simplify this whole procedure.

// Program Entry Point
int main() {

// Make x hold the binary representation of a
// positive infinity float
int x = 0x7f800000;

// Treat x as if it was holding a float instead of an int,
// and then give f the same value
float f = (float&)x;

// Print float f (positive infinity) to the console
cout << f << endl;
return 0;
}


This trick was taught to me by Jake Stine from pcsx2, and its something not a lot of coders know apparently, because I commonly see people using the ugly-way to do this instead.
Most-likely because you can't do this in normal C (just another reason why C sucks compared to C++ xD).

4 comments:

  1. Hi! I was just passing by and saw this post.

    I'm sorry, but what you're doing (both methods) is undefined behavior.
    It will _often_ work, but if you turn strict aliasing optimizations on, it may break and crash.

    The best method is to use a "union" to do this:

    union Foo
    {
    int i;
    float f;
    };

    int main()
    {
    Foo myFoo;

    myFoo.i = 0x7f800000;
    cout << myFoo.f << endl;

    return 0;
    }

    It's not an ugly way to it, works in C too, and will not occasionally break

    Cheers
    Dark Sylinc

    ReplyDelete
  2. I've kept thinking about it, and after more research; the "union" method is also non-standard. But the "union" method apparently works better with compilers with optimizations on (even strict aliasing) because it's easier for the compiler to identify.

    Well the moral of the story: watch out for strict aliasing rules in your compiler.
    Google "type punning" for more information.

    Hey at least you've found another way, didn't you?

    Cheers
    Dark Sylinc

    ReplyDelete
  3. Dark Sylinc:
    You're right it does break strict aliasing rules; and yes I'm familiar with the union-way.

    If you are to rely on this code you should have strict aliasing optimizations off if your compiler has such an option; From what I've read GCC seems to rely on the strict aliasing, whereas afaik msvc++ doesn't make such bold assumptions.
    At least I have not had a problem with msvc++ using such techniques, and I am not aware of an option to turn off such optimizations in msvc++, so I will assume it notices when code is breaking strict aliasing (in the above example it should be obvious to the compiler).

    Also, I work with a codebase that constantly breaks strict aliasing rules all over the place; if msvc++ had a big problem with it, our project would never work.

    ReplyDelete
  4. BTW, I found a msdn article where they also break strict-aliasing rules in a msvc example:
    http://msdn.microsoft.com/en-us/library/w22adx1s.aspx

    I guess this further-confirms that it should be safe on msvc++.

    ReplyDelete