1

C Noob here trying to follow along with some online lectures. In the professors example he shows us that we can read the data stored in an int as a float by doing the following: *(float*)&i. I tried doing this with the following code but nothing happens. I am testing it here: http://ideone.com/ExmXSW

#include <stdio.h>

int main(void) {
    // your code goes here
    int i=37;
    printf("%f", *(float*)&i);
    return 0;
}
wovano
  • 4,543
  • 5
  • 22
  • 49
Daniel Kobe
  • 9,376
  • 15
  • 62
  • 109

5 Answers5

6

This causes undefined behaviour:

  • Executing *(float *)&i violates the strict aliasing rule
  • The wrong format specifier was used: %i is for int, however you supplied a float

When code causes undefined behaviour, anything may happen. A lecture advising you to do this is a rubbish lecture unless it is specifically showing this as an example of what NOT to do. It is incorrect to say "we can read the data stored in an int as float" by this method.

NB. ideone.com is not great for testing because it suppresses a whole lot of compiler error messages, so you may think your code is correct when it in fact is not.

Community
  • 1
  • 1
M.M
  • 138,810
  • 21
  • 208
  • 365
  • Any recommendations for ideone.com alternatives? – Daniel Kobe Jan 21 '16 at 04:38
  • 1
    @DanielKobe install a compiler on your own system! Also there's [coliru](http://coliru.stacked-crooked.com/) although that doesn't have the input box option – M.M Jan 21 '16 at 04:42
1

What the professor may wanted to teach you that if you insert an integer in to a memory location (which represented by 32 bits in most machines) you can read it as a float (again 32 bits in most of the machines) but you will get different values. This is because integer is stored as a simple binary for example 0x000000001 is equals to integer 1 and 0x00000002 is for integer 2 etc.

However float representation in binary format is quite different. It is look like as follows:

bit  31 30    23 22                    0
     S  EEEEEEEE MMMMMMMMMMMMMMMMMMMMMMM

where S is the sign, E is for exponent and M is for mantissa.

Here is a bit of code that I was working on to help you understand this:

#include <stdio.h>

int main(void) {
    void* x = malloc(sizeof(int));
    int* y = x;
    float* z = x;
    *y=955555555;
    printf("%f", *z);
    return 0;
}

What I have done in this code is to allocate a memory and let variable y interpret it as integer and variable z interpret it as floating point. Now you can change y and see the that z has totally different value. In this case the output of the program is 0.000117.

You can also change variable z and see the same happens with variable y because both of them are pointing to the same memory location but interpreting it as different types.

Pooya
  • 6,083
  • 3
  • 23
  • 43
0

You need to use the correct format code. What you're doing is undefined behavior, but it would probably work if you changed the printf to:

printf("%f", *(float*)&i);

so it uses the correct format code. The problem is that, in modern x64 calling conventions, the first few values are passed in registers. But on x86-64 at least, it's a completely different set of registers for integer vs. floating point values, so using %i looks at a completely different register that has an effectively random value (it's deterministic only in the sense that you could examine the assembly to figure out what it will be, but not something you could guess from looking at the source code).

ShadowRanger
  • 143,180
  • 12
  • 188
  • 271
  • I doubt this is the case for vararg arguments as well. At least the documentation of the [Microsoft x64 calling convention](https://learn.microsoft.com/en-us/cpp/build/x64-calling-convention?view=vs-2019) states: "*For vararg or unprototyped functions, any floating point values must be duplicated in the corresponding general-purpose register.*" – wovano Aug 17 '19 at 16:10
0

There is absolutely NO correlation between the bits you would use to store 37 as an int and what would be interpreted when cast to a float.[1] Why? Integers (presuming 32-bit for sake of argument) are stored in memory as a simple binary representation of the number in question (subject to the endianness of the current machine), while (32-bit) floating point numbers are stored in IEEE-754 single-precision floating point format comprised of a single sign-bit, and 8-bit exponent (8-bit excess-127 notation) and a 23-bit mantissa (in a normalized "hidden-bit" format).

The only thing the integer and floating point number have in common is the fact they both occupy 32 bits of memory. That is the only reason you can, despite violating every tenant of strict-aliasing, cast a pointer to the integer value address and attempt to interpret it as a float.

Let's, for sake of argument, look at what you are doing. When you store 37 as in integer, on a little-endian box, you will have the following in memory:

00000000-00000000-00000000-00100101

When you interpret those 32-bits of memory by casting to float, you are attempting to interpret an IEEE-754 single-precision floating point value in memory of:

 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 1 0 1
|- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -|
|s|      exp      |                  mantissa                   |

when in reality, if you were looking at 37.0 in IEEE-754 single-precision floating point format, you would have the following in memory:

 0 1 0 0 0 0 1 0 0 0 0 1 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
|- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -|
|s|      exp      |                  mantissa                   |

If you compare what you are trying to look at as a float with what you should be looking at as a float, you should notice your cast results in floating point representation with a 0 value for the 8-bit exponent and a nonsensically small 23-bit mantissa. Integer 37 interpreted as a float results in a conversion so small it is virtually non-printable regardless how may significant digits you specify in the format.

The bottom line is there is no relation between the integer value in memory and what a floating number created from those same bits would be. (aside from a few computations not relevant here). An integer is an integer and a float is a float. The only thing their memory storage has in common is that on some machines they can both occupy 32-bits of memory. While you can abuse a cast and attempt to use the same bits as a floating point value - just be aware there is little to nothing in the way of correlation between the two interpretations.

footnotes:

[1] there are limited determinations of the next possible higher/lower valid floating point value that can be drawn by interpreting the memory occupied by a float point value as an integer.

David C. Rankin
  • 81,885
  • 6
  • 58
  • 85
0

You wrote that "nothing happens", but the code actually works as expected (with some assumptions about the architecture, such as that ints and floats are both 32-bit). At least it prints something, but the result is probably not what you expected.

On Ideone.com the printed output is 0.000000. The reason for this is that the integer value 37 interpreted as float value is 5.1848e-44. When using the "%f" format specifier in printf, this extremely small number will be rounded to zero. If you change the format string to "%e", the output would be 5.184804e-44. Or if you would change the value of i to 1078530010, the output would be 3.141593, for example.

(NB: Note that the value is actually first converted from float to double, and the double is passed to printf(). The "%f" format specifier also expects a double, not a float, so that works out well.)

There's certainly truth in many of the already posted answers. The code indeed violates the strict aliasing rule, and in general, the results are defined. This is mostly because data types can differ between different CPU architectures (different sizes, different endianness, etc.). Also, the compiler is allowed to make certain assumptions and try to optimize your code, causing the compiled executable to behave different than intended.

In practice, the intended behavior can be "forced" by using constructs such as volatile pointers, restricting the compilers ability to optimize the code. Newer versions of the C/C++ standard have even more advanced constructs for this. However, generally speaking, for a given target architecture of which you know the data sizes and formats, the code you posted can work correctly.

However, there is a better solution, and I'm surprised nobody has mentioned it yet. The only (somewhat) portable way to do this without breaking the strict aliasing rule, is using unions. See the following example (and demo at ideone.com):

#include <stdio.h>

int main(void) {
    union {
        int i;
        float f;
    } test;
    test.i = 1078530010;
    printf("%f", test.f);
    return 0;
}

This code also prints 3.141593, as expected.

wovano
  • 4,543
  • 5
  • 22
  • 49