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:

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