I've compiled this in gcc and g++ with pedantic and I don't get a warning in either one:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct a {
struct a *next;
int i;
};
struct b {
struct b *next;
int i;
};
struct c {
int x, x2, x3;
union {
struct a a;
struct b b;
} u;
};
void foo(struct b *bar) {
bar->next->i = 9;
return;
}
int main(int argc, char *argv[]) {
struct c c;
memset(&c, 0, sizeof c);
c.u.a.next = (struct a *)calloc(1, sizeof(struct a));
foo(&c.u.b);
printf("%d\n", c.u.a.next->i);
return 0;
}
Is this legal to do in C and C++? I've read about the type-punning but I don't understand. Is foo(&c.u.b)
any different from foo((struct b *)&c.u.a)
? Wouldn't they be exactly the same? This exception for structs in a union (from C89 in 3.3.2.3) says:
If a union contains several structures that share a common initial sequence, and if the union object currently contains one of these structures, it is permitted to inspect the common initial part of any of them. Two structures share a common initial sequence if corresponding members have compatible types for a sequence of one or more initial members.
In the union the first member of struct a
is struct a *next
, and the first member of struct b
is struct b *next
. As you can see a pointer to struct a *next
is written, and then in foo a pointer to struct b *next
is read. Are they compatible types? They're both pointers to a struct and pointers to any struct should be the same size, so they should be compatible and the layout should be the same right? Is it ok to read i
from one struct and write to the other? Am I committing any type of aliasing or type-punning violation?
What you could do (and i've been bitten by this before), is declare both struct's initial pointer to be
void*
and do casting. Since void is convertible to/from any pointer type, you would only be forced to pay an ugliness tax, and not risk gcc reordering your operations (which I've seen happen -- even if you use a union), as a result of compiler bugs in some versions. As @T.C. correctly points out, layout compatibility of a given type means that at the language level they are convertible; even if types might incidentally have the same size they are not necessarily layout compatible; which might give some greedy compilers to assume some other things based on that.Yes, this is fine; the bolded part of the quote in your question covers this case.
I've had a similar question some time ago, and I think I can answer yours.
Yes,
struct a
andstruct b
are not compatible types, and pointers to them are also incompatible.Yes, what you are doing is illegal even from the outdated point of view of the C89 standard. However, it may be interesting to note that if you reverse the order of elements in
struct a
andstruct b
, you would be able to accessint i
of astruct c
instance (but not access itsnext
pointer in any way, i.e.bar->i = 9;
instead ofbar->next->i = 9;
), but only from the C89 standard's point of view.But even if you'll reverse the order of elements in the two
struct
s, what you're doing would still be illegal from the point of view of the C99 and C11 standards (as interpreted by the commitee). In C99, the part of the standard you have quoted has been changed to this:The last phrase is a bit ambiguous, since you can interpret "visible" in several ways, but, according to the commitee, this means that the inspection should be performed on an object of the union type in question.
So, in your case, the correct way to handle this would be something along the lines of:
That is all fine and interesting from the language-lawyer point of view, but in reality, if you don't apply different packing settings to them,
struct
s with the same initial sequence will have it with the same layout (in 99.9% of cases). Actually, they should have the same layout even in your original setup, since the pointers tostruct a
andstruct b
should have the same size. So, if your compiler doesn't get nasty when you break strict aliasing, you can more-or-less safely typecast between them, or use them in a union the way you're using them now.EDIT: as noted by @underscore_d in the comments to this answer, since the appropriate clauses in the C++ standards do not have the line "anywhere that a declaration of the completed type of the union is visible" in their appropriate parts, it may be possible that the C++ standard has the same stance on the subject as the C89 standard.
In C:
struct a
andstruct b
are not compatible types. Even ins1
ands2
are not compatible types. (See example in 6.7.8/p5.) An easy way to identify non-compatible structs is that if two struct types are compatible, then something of one type can be assigned to something of the other type. If you would expect the compiler to complain when you try to do that, then they are not compatible types.Therefore,
struct a *
andstruct b *
are also not compatible types, and sostruct a
andstruct b
do not share a common initial subsequence. Your union-punning is instead governed by the same rule for union punning in other cases (6.5.2.3 footnote 95):In C++,
struct a
andstruct b
also do not share a common initial subsequence. [class.mem]/p18 (quoting N4140):[basic.types]/p9:
struct a *
andstruct b *
are neither structs nor unions nor enumerations; therefore they are only layout-compatible if they are the same type, which they are not.It is true that ([basic.compound]/p3)
But that does not mean those pointer types are layout-compatible types, as that term is defined in the standard.