Question

#include <stdio.h>
#include <stdlib.h>

typedef union
{

    double f;

    unsigned long long u;

    int long long i;
} r;

int main()
{
  r var1, var2;

  var1.f = -3.5;
  var2.u = 3;

  var1.u = var1.u + var2.u;

  printf("%u", var1.u);
  return 0;
}

Why is this returning only the value of var1 and not the summation? It works if var1 and var2 are added with the same assigned datatype. I thought union made that a non issue?

Was it helpful?

Solution 2

Type-punning through unions has been legal since C89 and so there is no undefined behavior there and several compilers explicitly guarantee it will work, for example see gcc documentation on type-punning. They need this because in C++ it is not as clear cut.

But this line sure does have undefined behavior:

printf("%u", var1.u);

The type of var1.u is unsigned long long and so the correct format specifier should be %llu, and clang duly complains as follows:

warning: format specifies type 'unsigned int' but the argument has type 'unsigned long long' [-Wformat]

printf("%u", var1.u);
        ~~   ^~~~~~
        %llu

Once you fix that the output I see is this (see it live):

13838435755002691587

which shows that the changes to both variables are having an effect.

The results you see are due to the format of IEEE 754 binary number which looks like this:

enter image description here

This is one of the several examples showing the hex representation of a number:

3ff0 0000 0000 000216 ≈ 1.0000000000000004

c000 0000 0000 000016 = –2

So in your case assigning a negative number to var1.f is going to set at least one high bit. We can explore this in C++ easily using std::bitset and gcc since they explicitly support type-punning via unions in C++:

#include <iostream>
#include <iomanip>
#include <bitset>
#include <string>

typedef union
{
    double f;
    unsigned long long u;
    int long long i;
} r;

int main() 
{
    r var1, var2;

    var1.f = -2 ; // High bits will be effected so we expect a large number for u
                  // Used -2 since we know what the bits should look like from the
                  // example in Wikipedia
    std::cout << var1.u << std::endl ;

    std::bitset<sizeof(double)*8> b1( var1.u ) ;
    std::bitset<sizeof(double)*8> b2( 13835058055282163712ull ) ;

    std::cout << b1 << std::endl ;
    std::cout << b2 << std::endl ;

    var2.u = 3;

    var1.u = var1.u + var2.u; // Low bits will be effected so we expect a fraction
                              // to appear in f

    std::cout << std::fixed << std::setprecision(17) <<  var1.f << std::endl ;

    std::bitset<sizeof(double)*8> b3( var1.u ) ;
    std::bitset<sizeof(double)*8> b4( 13835058055282163715ull ) ;

    std::cout << b3 << std::endl ;
    std::cout << b4 << std::endl ;

    return 0;
}

the results I see are (see it live):

13835058055282163712
1100000000000000000000000000000000000000000000000000000000000000
1100000000000000000000000000000000000000000000000000000000000000
-2.00000000000000133
1100000000000000000000000000000000000000000000000000000000000011
1100000000000000000000000000000000000000000000000000000000000011

OTHER TIPS

Reading from a different member of a union than the one that you last assigned results in an unspecified value. It's not invalid, but the standard doesn't specify how the type punning will be resolved, and the result might be a trap represnetation.. See:

Is type-punning through a union unspecified in C99, and has it become specified in C11?

The purpose of unions isn't to allow type punning. It allows you to save space, by reusing the same memory for two different variables when you know you'll never need them both at the same time. For an example where this is useful see:

How can a mixed data type (int, float, char, etc) be stored in an array?

(that happens to be my highest-voted answer).

Umm.

In a union fields reside on the same physical space. That is, the size of union is roughly the size of its biggest field. You assign to a float field of a union and then tries to use as integer value. This leads to an undefined behaviour, more precisely this behaviour depends on representation of integer and float numbers on a target platform.

Frankly speaking you may use this trick for certain kind of conversions (e.g. if you need to "convert" a pair of machine words to a single dword), but every time you use this technique you should clearly understand the gory details of target CPU architechture.

A friend of mine once got spurious segfaults on SPARC computers because he tried to access a non-aligned data using similar techniques :)

The example:

alex@galene ~/tmp $ cat test_union.c 
#include <stdio.h>

typedef union {
        float f;
        unsigned long long ull;
} csome;

int main(void) {
        csome cs;
        csome cs2;
        printf("&f = %p, &ull = %p\n", &cs.f, &cs.ull);
        cs.f = 3.5;
        cs2.ull = 3;
        cs2.ull = cs.ull + cs2.ull;
        printf("cs2.ull = %Ld\n", cs2.ull);
        return 0;
}
alex@galene ~/tmp $ cc -Wall -o test_union test_union.c
alex@galene ~/tmp $ ./test_union 
&f = 0xbfee4840, &ull = 0xbfee4840
cs2.ull = 1080033283

As you may see the value of cs2.ull is "random"

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