4 ms·
It works when unions are used correctly. If they are used to deliberately subvert the type system (e.g., put in an int, get an array of char out) it doesn't wor
by andybalholm 12y ago
It works when unions are used correctly. If they are used to deliberately subvert the type system (e.g., put in an int, get an array of char out) it doesn't work. But C has so many other ways to subvert the type system that there's no need to do that.
Probably about the only place you'll see them used like that is in the code produced by web2c in compiling TeX. Knuth used variant records a lot to get around Pascal's type safety, and they get translated to unions.
- piokuc 12y agoThat's why I said "not always". Here is an example which will print different result if you define #define union struct Here you go: #include <stdio.h> union U { char a; char b; }; int main() { U u; u.a = 13; u.b = 10; printf("%d\n", int(u.a)); return 0; }
- to3m 12y agoI don't think a and b need necessarily overlap. I think the types need to be more like this: struct A {char a;}; struct B {char a;}; union U {struct A a; struct B b;};
- rsc 12y agoThat code is not valid according to the C standard, so there is no guarantee it will work anywhere. In particular, many modern compilers have optimizations that would break that code. I would be a little surprised if modern web2c still uses unions this way and gets away with it. The only standard compliant way to, say, convert a float to an int is to use memmove: uint32 i; float32 f; i = 0x80000000; memmove(&f, &i, 4);
- andybalholm 12y agoI finally managed to track down the definition of memoryword from texlive-20130530-source/texk/web2c/texmfmem.h. Here it is: typedef union { #ifdef TeX glueratio gr; twohalves hh; #else twohalves hhfield; #endif #ifdef XeTeX voidpointer ptr; #endif #ifdef WORDS_BIGENDIAN integer cint; fourquarters qqqq; #else /* not WORDS_BIGENDIAN */ struct { #if defined (TeX) && !defined (SMALLTeX) || defined (MF) && !defined (SMALLMF) || defined (MP) && !defined (SMALLMP) halfword junk; #endif /* big {TeX,MF,MP} */ integer CINT; } u; struct { #ifndef XeTeX #if defined (TeX) && !defined (SMALLTeX) || defined (MF) && !defined (SMALLMF) || defined (MP) && !defined (SMALLMP) halfword junk; #endif /* big {TeX,MF,MP} */ #endif fourquarters QQQQ; } v; #endif /* not WORDS_BIGENDIAN */ } memoryword; Once you sort through all the ifdefs and typedefs, it boils down to a union of a float (glueratio), an int32 (integer), a pair of int16s (twohalves), and a struct of 4 bytes (fourquarters). When TeX creates its memory dump file, it does it all in terms of the bytes in the fourquarters struct. (And I think there are other times that it does similar things.) This may very well be undefined behavior, but it seems to work when compiled with GCC anyway. Thankfully gc isn't such crazy code as this.
- to3m 12y agoC99 6.5.2.3.5 seems to suggest that non-overlapping union members could break conforming code: ``One special guarantee is made in order to simplify the use of unions: if a union contains several structures that share a common initial sequence (see below), and if the union object currently contains one of these structures, it is permitted to inspect the common initial part of any of them anywhere that a declaration of the complete type of the union is visible. Two structures share a common initial sequence if corresponding members have compatible types (and, for bit-fields, the same widths) for a sequence of one or more initial members.''
- bzbarsky 12y agoWhich part of the C standard would forbid type punning a uint32 to a float32? http://www.open-std.org/jtc1/sc22/wg14/www/docs/dr_283.htm http://www.open-std.org/jtc1/sc22/wg14/www/docs/dr_283.htm suggests that C89 explicitly allowed this, C99 at first glance did not, and was errata'ed to make it clear that this is still allowed. C11 seems to have the same verbiage.
- Someone 12y agoDevil's advocate example: union hack { int x; float y}; int foo( int *i, float *f) { *i = 3; *f = 42.0; return *i; } Aliasing rules say foo need not read from that int pointer, may switch the order of the write to f and the write to i, and can assume that foo returns 3, so struct hack h; foo( &h.i, &h.f); might return 3 or something else. I think that last call introduces undefined behavior, but only becuase of the definition of foo that the writer of that call might not even have the source for. But of course, that is an "you shouldn't do that" edge case. One could also claim that the corrigendum doesn't apply because foo doesn't "use a member to access the contents of a union".
- bzbarsky 12y agoSure, but that's an aliasing issue, not a type punning issue. I agree that the code you cite there is a violation of the aliasing rules, and will not work "as expected" on modern compilers unless one does the equivalent of gcc's -fno-strict-aliasing. And I agree that the corrigendum doesn't apply in this case. Once you hand different-typed pointers to the same memory to people, whether via union or just casting pointers, the aliasing rules will up and bite you.