It appears you're right. Nevertheless, won't this mean that, indirectly,
the standard guarantees that, when different structs have a common initial
sequence, the members that represent a common initial sequence can be
accessed no matter which type the object is casted to?
For example, consider the following:
<code>
#include <stdlib.h>
#include <stdio.h>
struct Foo
{
int a;
int b;
};
struct Bar
{
int a;
int b;
float c;
};
void processFoo(struct Foo *foo)
{
printf("foo.a: %d\tfoo.b: %d\n",foo->a, foo->b);
}
void processBar(struct Bar *bar)
{
printf("bar.a: %d\tbar.b: %d\n",bar->a, bar->b);
}
int main(void)
{
union
{
struct Foo foo;
struct Bar bar;
} baz;
struct Foo foo2;
struct Bar bar2;
baz.foo.a = 1;
baz.foo.b = 2;
foo2.a = 1;
foo2.b = 2;
bar2.a = 1;
bar2.b = 2;
processFoo(&baz.foo);
processBar(&baz.bar);
processFoo(&foo2);
processFoo(&bar2);
Diagnostic required here, as in a similar case from your earlier
post. Do these diagnostics make no impression on you, not even so
much as to raise a teeny-tiny doubt about the validity of what
you're trying to do?
return EXIT_SUCCESS;
}
</code>
If the layout of baz.foo and foo2, as well as baz.bar and bar2, is
guaranteed to be the same, and both foo2 and bar2 are stand-alone objects
which weren't defined in a union type, doesn't this guarantee the access to
the "common initial sequence" whether an object is casted to struct Foo or
struct Bar?
Let's start by dismissing the layout issue (I think we can do
this). Argument: Suppose struct Foo and struct Bar are declared
identically in modules x.c and y.c, but only in x.c do they appear
in a union. Since corresponding structs in x and y are compatible
(6.2.7p1) they must be arranged identically: An x function can
pass a pointer to an instance of x's struct Foo to a y function,
where the representation must be the same as in y. Therefore the
union membership in x cannot influence the compiler's choice of
how to lay out a struct Foo, because it must end up with the same
layout as is used in union-free y. Layout is not the issue.
I'm no code-generation and optimization expert, but I think the
"special guarantee" is about aliasing, not about representation. In
the absence of a union containing both struct Foo and struct Bar,
the compiler can assume that the elements in instances of the two
are distinct: The bytes in a certain memory area represent the value
of a struct Foo *or* of a struct Bar, not both. (This is just like
other types: Some batch of bytes belongs to an int *or* to a double,
and unless there's a union in the picture they cannot belong to both.)
If you use type-punning to access the bytes via a "foreign" type, the
compiler is not obliged to notice or respect the pun (6.5p7; some
specific puns are permitted, but not all).
The code in your post would be, I think, entirely well-behaved
and well-defined if the third processFoo() call were removed. With
the third call in place, it runs afoul of 6.5.2.2p2, violating a
"shall" in a Constraints clause. If you were to add a cast you'd
avoid the 6.5.2.2p2 issue, but 6.5p7 still operates.
What you're doing is "likely to work" in simple cases and with
compilers that don't optimize aggressively. But remember: Memory
is s-l-o-w compared to CPU's, so compiler writers have a large and
growing incentive to find clever ways to avoid accesses. (I write,
by the way, from experience: More than fifteen years ago a compiler
of my acquaintance optimized a pun not unlike yours, producing code
that caught intermittent and hard-to-reproduce SIGSEGV's; it took
three engineers a week and a half to track down the trouble.)