1

What this code mean? i mean it's seem that we take the address of a variable and cast it to a (pointer type), after that we deferencing it, as we will know the value. Am I wrong?

#include "stdio.h"

int main(void) {

  int numI = 3;
  float numF = * (float *)&numI;

  printf("%f", numF); 

  numF = 3.0;
  numI = * (int*)&numF;

  printf("\n%d", numI); 

  return 0;
}
Robert
  • 554
  • 5
  • 13
  • violating strict aliasing rules can lead to strange things... – UnholySheep Mar 23 '18 at 21:24
  • 1
    Possible duplicate of [What is the strict aliasing rule?](https://stackoverflow.com/questions/98650/what-is-the-strict-aliasing-rule) – UnholySheep Mar 23 '18 at 21:24
  • Related, but not exactly: https://www.h-schmidt.net/FloatConverter/IEEE754.html, stolen from https://stackoverflow.com/a/28428118/2988730 – Mad Physicist Mar 23 '18 at 21:29
  • Using Visual Studio 2015 the results I get are 0.000000 and 1077936128. Watching the variables in the debugger, I see `numF` with an illegal float value which the `printf()` seems to be turning into zero for the first printed value. – Richard Chambers Mar 23 '18 at 21:34

1 Answers1

2

Consider these steps:

  • numI and numF are 2 objects that happen to have the same size on your architecture, namely 4 bytes.
  • &numI is an expression with type int * and its value is the address of the object numI.
  • Casting it with (float *)&numI is an expression of type float * with the same value. Aking to telling the compiler, this address is that of a float.
  • Dereferencing this expression *(float *)&numI produces a value of type float that depends on the actual representation of int for the value 3. For example, on the intel core processors (little endian, 32-bit, 2s complement) the bytes at address intI would contain 03 00 00 00. float objects are represented in memory on the same processor using the IEEE-756 Standard: 03 00 00 00 represents the very small value 1.5 x 2-148, approximately 4.2039e-45.
  • Passing this value as variable argument to printf first converts it to type double, with the same value and printf converts the value to 0.000000 because the format %f specifies only 6 decimal places and no exponent. In order to get a more precise conversion, you could use %g which would produce 4.2039e-45, or %a which would produce the hexadecimal representation 0x1.8p-148.
  • The second part of the program performs the opposite conversion: the float value 3.0F whose IEEE-756 representation is 00 00 40 40 is reinterpreted as an int, producing the value 1077936128.

Here is a modified version of your program that makes it more explicit:

#include <assert.h>
#include <math.h>
#include <stdio.h>
#include <string.h>

int main(void) {
    int numI;
    float numF;
    unsigned char *p;

    assert(sizeof numI == sizeof numF);

    numI = 3;
    p = (unsigned char *)&numI;
    printf("int value %d is represented in memory as %02X %02X %02X %02X\n",
           numI, p[0], p[1], p[2], p[3]);

    //numF = *(float *)&numI;
    memcpy(&numF, &numI, sizeof numF);
    printf("reinterpreted as float with format %%f: %f\n", numF);
    printf("reinterpreted as float with format %%g: %g\n", numF);
    printf("reinterpreted as float with format %%a: %a\n", numF);
    printf("numF exact value: %g * 2^-148\n", numF * pow(2.0, 148));

    numF = 3.0;
    p = (unsigned char *)&numF;
    printf("float value %.1g is represented in memory as %02X %02X %02X %02X\n",
           numF, p[0], p[1], p[2], p[3]);

    //numI = *(int *)&numF;
    memcpy(&numI, &numF, sizeof numI);
    printf("reinterpreted as int with format %%d: %d\n", numI);
    printf("reinterpreted as int with format %%#X: %#X\n", numI);

    return 0;
}

Output:

int value 3 is represented in memory as 03 00 00 00
reinterpreted as float with format %f: 0.000000
reinterpreted as float with format %g: 4.2039e-45
reinterpreted as float with format %a: 0x1.8p-148
numF exact value: 1.5 * 2^-148
float value 3 is represented in memory as 00 00 40 40
reinterpreted as int with format %d: 1077936128
reinterpreted as int with format %#X: 0X40400000

Note however that:

  • These casts violate the strict aliasing rule, hence have undefined behavior.
  • The representation in memory of types int and float are architecture specific. Some systems represent int with big-endian byte order, some have padding bits and trap values, some vintage systems even used one's complement or sign+magnitude representations. The representations for float can be even more exotic, although most current systems use IEEE-756 with the same byte ordering as integers. So your program has undefined behavior again, and at best produces implementation specific output.
  • The recommended method for reinterpreting the representation in memory is to copy the bytes with memcpy, as shown in the modified code. Modern compilers will produce very efficient code for this, expanding the memcpy to a mere 2 instructions if not less.
chqrlie
  • 131,814
  • 10
  • 121
  • 189