double d = 3.14;
int64_t *p = (int64_t *) &d; // !!!
int64_t n = *p;
The union aliasing way would be to makeset the double field in a union and read the integer field back: typedef union {
double d;
int64_t n;
} int_or_double;
int_or_double u;
u.d = 3.14
int64_t n = u.n
According to the standard, the union aliasing approach is allowed, but the type punning one is undefined behavior.---------
The standard team decided to forbid type punning to allow compilers to implement more optimizations. They still allow union aliasing because they felt there should still be a way to do low-level type casts if for those that absolutely want to.
The standard only allows reading a value as the type it was written with or as char, and an access as char is only good for copying. If a char access used different bit order, that would be OK according to the standard because you couldn't tell unless you violated the standard.
It seems every compiler will tolerate a memcpy for type punning, even though this isn't required.
Having said that, it is possible to implement memcopy in standard C: char pointers have an explicit special exemption to the aliasing rule so you are allowed to inspect objects as array of chars.
double d = 3.14;
uint64_t i;
memcpy(&i, &d, sizeof(d));
is perfectly kosher.Aren't exact-bitwidth integer types optional even with C99 (let alone preceding versions of standard)?
If an implementation can't represent uint64_t exactly, it can still have a uint_least64_t type of whatever width works.
a) I believe sizeof (double) * 8 == 64 is not enforced by the standard either. One could argue that it is hard to find anything else these days, but (for a contrived example) I believe nothing prevents you from making your own port of GCC for some purpose, and make 'double' be 'float'. (From my memories, it is even far enough from rocket science.)
b) It already happened to me personally, to work with code where people have implemented their own uintXX types (because of the lack of the standard ones), and then over time these became narrower/vider.
So if we would like to have it portable, I think, nothing beats:
union { double d; char cc [sizeof (double)]; };
and there we are, back to what is in the standard.
sizeof (double) can be guaranteed to be 8 if __STDC_IEC_559__ is defined, so checking that is an additional hurdle if you want to use memcpy for punning. That said, I think it can be assumed that you're writing non-portable code for a specific set of platforms already if you are punning floats to integers, because the representation is implementation dependent in the first place. Probably you also know the environments in which it will be compiled as well, so the example is a bit contrived.
Very much agree that unions are the easiest and standard way to do type punning. There's so many misconceptions about this somehow being unclear in the standard. Sure, it does reflect poorly on the quality of the standard that Linux contributors don't understand it and that Linus Torvalds actively ignores it, but if you know what you're looking for it's often not that hard to find a definite answer.
That's why Linus writes:
"So the commit message that talks about how horrible union aliasing is is pushing a story that is simply wrong."
The commit message with which he disagreed obviously explicitly wrote something like (paraphrasing) "using union is undefined behavior by standard, so we are changing the code to not use it" etc.
Linus also wrote: "I'm not talking about the changes themselves - I can live with them. But the _rationale_ is pure and utter garbage, and dangerously so."
The rationale was (note, I don't have exact quote, I'm paraphrasing, if somebody has the original please give a link) that using unions for that is bad as per standard. Which is what the standard says but which is for practical purposes simply wrong. The standard is wrong an should not be followed, that was main Linus' argument.
IMO there are 4 highly relevant paragraphs in C99:
6.2.6.1: 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.
6.2.6.1: 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
6.5.2.3 DR283) If the member used to access the contents of a union object is not the same as the member last used to store a value in the object, the appropriate part of the object representation of the value is reinterpreted as an object representation in the new type as described in 6.2.6 (a process sometimes called "type punning").
6.7.2.1: A pointer to a union object, suitably converted, points to each of its members (or if a member is a bit-field, then to the unit in which it resides), and vice versa.
None of this implies that unions can't be used for type punning. What it does imply is that reading from a larger member of the union than you last wrote to will leave you with an unspecified value.
gcc 4.9.2 for sure does not (-O3 -fstrict-aliasing). Not sure what clang does, but I'd be surprised if it ignores the standard. The standard makes it pretty clear what meaning it will have. Sure, gcc and clang maintainers may seem to read the standards like a lawyer looking to void a contract, but in this case it is actually very clear what they should do.
> what is not said that it “must work” in spite of other “must work” and “undefined” standard statements.
The standard makes it clear how to deal with the semantics: "In the abstract machine, all expressions are evaluated as specified by the semantics. An actual implementation need not evaluate part of an expression if it can deduce that its value is not used and that no needed side effects are produced (including any caused by calling a function or accessing a volatile object)." Only constraints directly use "shall"/"shall not" type language, but the expressions should still be evaluated as specified by the semantics.
“GNU extensions to standard C++ (and to C90) do explicitly allow type-punning with unions. Other compilers that don't support GNU extensions may also support union type-punning, but it's not part of the base language standard.”
Also, apparently in C99 one relevant statement was:
“The value of a union member other than the last one stored into [is unspecified]”
https://stackoverflow.com/questions/25664848/unions-and-type...
Note: In case it's not obvious, I, personally, believe that, at least, using unions for type-punning has to exist and should be explicitly supported in the standards too. And that the compiler contributors should not produce practically unusable results even when some standard is wrong (for common use) or unclear. But the quotes like the above give a hint on which basis some try to cling to bad approaches in this special case. In other cases it's probably even harder to avoid unwanted results.