Peeking at Data Stored in a Union

A C language union is a shared data space accessed via different data types. This feature can be handy, providing that you tag the union data as was seen in this Lesson. But I’m after exploiting a union for a more abusive yet useful purpose.

My goal is to exploit a union’s weakness by accessing a specific data type through another data type to reveal out real numbers are stored in binary. The union provides a tool I can use to “peek under the hood” and see the binary format directly. But first, consider the following example:

2026_10_17-Lesson.c

#include <stdio.h>

int main()
{
    union real {
        float a;
        long b;
        char c[8];
    };
    union real numb;
    int x;

    numb.a = 10.0;

    printf("The value of numb.a is %f\n",numb.a);
    printf("The value of numb.b is 0x%0lX\n",numb.b);
    printf("Union numb occupies %lu bytes:",sizeof(numb));
    for( x=0; x<8; x++ )
        printf(" %02X",numb.c[x]);
    putchar('\n');

    return 0;
}

In the code, union real contains three data types all sharing the same storage: a float, a long, and a four-char array.

The program assigns the value 10.0 to float variable numb.a. This same value is output as a long stores it (in numb.b) and then the individual bytes are output as stored in the char array numb.c[]:

The value of numb.a is 10.000000
The value of numb.b is 0x41200000
Union numb occupies 8 bytes: 00 00 20 41 00 00 00 00

Variable numb.a is assigned real number 10.0. When read as a long int, the value is 0x41200000. This value reflects the way real numbers are stored in binary, so it’s not the same as 0x0A or 10 decimal.

The output for char array numb.c[] is interesting to me. It shows how the bytes appear in memory for the real number 10.0. But the values are output backwards as a long int, which reflects the way CPUs store data.

It’s all fascinating stuff, but what intrigues me the most is how binary data represents a real number. The value 10 is stored as 0x41200000. So what’s going on with those bits that cause them to be interpreted as 10.0? And what about the precision or accuracy of the value? How is that accounted for?

In next week’s Lesson I cover the mystery of how real numbers are stored in binary.

3 thoughts on “Peeking at Data Stored in a Union”

  1. Are you also planning to show unions and bit-fields can be combined to access specific parts of encoded values? I.e. for IEEE 754 binary32 something like the following:

    typedef union {
      float        float_val;
      unsigned int int_val;
      struct {
        #if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
        unsigned int fraction : 23;
        unsigned int biased_exponent : 8;
        unsigned int sign : 1;
        #else
        unsigned int sign : 1;
        unsigned int biased_exponent : 8;
        unsigned int fraction : 23;
        #else
        #error “Unsupported byte order.”
        #endif
      } parts;
    } ieee754f32;

    Of course, to ensure this works the startup code should probably verify the value of the individual fields against a known value:

    #include <assert.h>

    int main (void)
    {
      /* Check at startup to ensure bit field values are correct: */
      { ieee754f32 check;  
        _Static_assert(sizeof(((ieee754 *)0)->parts) == 4);

        check.float_val = -1.5f;
        assert (check.parts.sign == 1);
        assert (check.parts.biased_exponent == 127);
        assert (check.parts.fraction == 0x400000);
      }
      ⋮
      return (0);
    }

    If the above assert() statements donʼt cause the program to abort, then accessing a stored binary32 floating-point value via the given bit-fields should work. That notwithstanding the fact that the C standard unfortunately leaves compilers some leeway regarding the storage unit size as well as the arrangement of the individual elements. Here is the relevant part from 6.7.2.1 §10 of the C99 standard (emphasis is mine):

    “An implementation may allocate any addressable storage unit large enough to hold a bit- field. If enough space remains, a bit-field that immediately follows another bit-field in a structure shall be packed into adjacent bits of the same unit. If insufficient space remains, whether a bit-field that does not fit is put into the next unit or overlaps adjacent units is implementation-defined. The order of allocation of bit-fields within a unit (high-order to low-order or low-order to high-order) is implementation-defined. The alignment of the addressable storage unit is unspecified.”

  2. Well, that code is certainly more impressive than what I’ve come up with! No, I’m using bit twidding to separate out the pieces and parts. Stay tuned for the fun.

  3. To be honest, since the C standard is so vague regarding the order the elements of a bit-field are stored, bitwise operations are the recommended approach anyway. Itʼs just that bit fields are just way cooler 😉

    That said, the solution above should also be quite robust, provided one takes endianness into account. (Thatʼs where I made a mistake in the given snippet. The first #else should of course be #elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ instead… but I guess a good programmer should be able to adapt. Note to self: never post code without testing it first ☺)

Leave a Reply