Reputation: 3171
Consider this program:
#include <stdio.h>
union myUnion
{
int x;
long double y;
};
int main()
{
union myUnion a;
a.x = 5;
a.y = 3.2;
printf("%d\n%.2Lf", a.x, a.y);
return 0;
}
Output:
-858993459
3.20
This is fine, as the int
member gets interpreted using some of the bits of the long double
member. However, the reverse doesn't really apply:
#include <stdio.h>
union myUnion
{
int x;
long double y;
};
int main()
{
union myUnion a;
a.y = 3.2;
a.x = 5;
printf("%d\n%.2Lf", a.x, a.y);
return 0;
}
Output:
5
3.20
The question is why the long double
doesn't get reinterpreted as some garbage value (since 4 of its bytes should represent the integer)? It is not a coincidence, the program outputs 3.20 for all values of a.x
, not just 5.
Upvotes: 10
Views: 164
Reputation: 145089
Answers posted by Mohit Jain, Kaz and JL2210 provide good insight to explain your observations and investigate further, but be aware that the C Standard does not guarantee this behavior:
6.2.6 Representations of types 6.2.6.1 General
6 When a value is stored in an object of structure or union type, including in a member object, the bytes of the object representation that correspond to any padding bytes take unspecified values. The value of a structure or union object is never a trap representation, even though the value of a member of the structure or union object may be a trap representation.
7 When a value is stored in a member of an object of union type, the bytes of the object representation that do not correspond to that member but do correspond to other members take unspecified values.
As a consequence, the behavior described in the answers is not guaranteed as all the bytes of the long double y
member could be modified by setting the int x
member, including the bytes that are not part of the int
. These bytes can take any value and the contents of y
could even be a trap value, causing undefined behavior.
As commented by Kaz, gcc is more precise than the C Standard: the documentation notes it as a common practice: The practice of reading from a different union member than the one most recently written to (called type-punning) is common. Even with -fstrict-aliasing
, type-punning is allowed, provided the memory is accessed through the union type. This practice is actually condoned in the C Standard since C11, as documented in this answer: https://stackoverflow.com/a/11996970/4593267 . Yet in my reading of this footnote there is still no guarantee about the bytes of y
not part of x
.
Upvotes: -1
Reputation: 30489
The size of long double
is very large. To see the effect of modifying the x
field on implementations where x
lines up with the LSBs of the mantissa of y
and other bits of union are not effected when modifying via x
, you need to print the value with much higher precision.
Upvotes: 6
Reputation: 15586
This is only affecting the last half of the mantissa. It won't make any noticeable difference with the amount of digits you're printing. However, the difference can be seen when you print 64 digits.
This program will show the difference:
#include <stdio.h>
#include <string.h>
#include <ctype.h>
union myUnion
{
int x;
long double y;
};
int main()
{
union myUnion a;
a.y = 3.2;
a.x = 5;
printf("%d\n%.64Lf\n", a.x, a.y);
a.y = 3.2;
printf("%.64Lf\n", a.y);
return 0;
}
My output:
5
3.1999999992549419413918193599855044340074528008699417114257812500
3.2000000000000001776356839400250464677810668945312500000000000000
Based on my knowledge of the 80-bit long double
format, this overwrites half of the mantissa, which doesn't skew the result much, so this prints somewhat accurate results.
If you had done this in my program:
a.x = 0;
the result would've been:
0
3.1999999992549419403076171875000000000000000000000000000000000000
3.2000000000000001776356839400250464677810668945312500000000000000
which is only slightly different.
Upvotes: 4
Reputation: 58627
However, the reverse doesn't really apply
On a little endian system (least significant byte of a multi-byte value is at the lowest address), the int
will correspond to the least significant bits of the mantissa of the long double
. You have to print that long double
with a great deal of precision to see the effect of that int
on those insignificant digits.
On a big endian system, like a Power PC box, things would be different: the int
part would line up with the most significant part of the long double
, overlapping with the sign bit, exponent and most significant mantissa bits. Thus changes in x
would have drastic effects on the observed floating-point value, even if only a few significant digits are printed. However, for small values of x
, the value appears to be zero.
On a PPC64 system, the following version of the program:
int main(void)
{
union myUnion a;
a.y = 3.2;
int i;
for (i = 0; i < 1000; i++) {
a.x = i;
printf("%d -- %.2Lf\n", a.x, a.y);
}
return 0;
}
prints nothing but
1 -- 0.0
2 -- 0.0
[...]
999 - 0.0
This is because we're creating an exponent field with all zeros, giving rise to values close to zero. However, the initial value 3.2 is completely clobbered; it doesn't just have its least significant bits ruffled.
Upvotes: 8