Given the declarations:
union U { int x; } u,*up = &u;
struct S { int x; } s,*sp = &s;
the lvalues u.x
, up->x
, s.x
, and sp->x
are all of type int
, but any access to any of those lvalues will (at least with the pointers initialized as shown) will also access the stored value of an object of type union U
or struct S
. Since N1570 6.5p7 only allows objects of those types to be accessed via lvalues whose types are either character types, or other structs or unions that contain objects of type union U
and struct S
, it would not impose any requirements about the behavior of code that attempts to use any of those lvalues.
I think it's clear that the authors of the Standard intended that compilers allow objects of struct or union types to be accessed using lvalues of member type in at least some circumstances, but not necessarily that they allow arbitrary lvalues of member type to access objects of struct or union types. There is nothing normative to differentiate the circumstances where such accesses should be allowed or disallowed, but there is a footnote to suggest that the purpose of the rule is to indicate when things may or may not alias.
If one interprets the rule as only applying in cases where lvalues are used in ways that alias seemingly-unrelated lvalues of other types, such an interpretation would define the behavior of code like:
struct s1 {int x; float y;};
struct s2 {int x; double y;};
union s1s2 { struct s1 v1; struct s2 v2; };
int get_x(void *p) { return ((struct s1*)p)->x; }
when the latter was passed a struct s1*
, struct s2*
, or union s1s2*
that identifies an object of its type, or the freshly-derived address of either member of union s1s2
. In any context where an implementation would see enough to have reason to care about whether operations on the original and derived lvalues would affect each other, it would be able to see the relationship between them.
Note, however, that that such an implementation would not be required to allow for the possibility of aliasing in code like the following:
struct position {double px,py,pz;};
struct velocity {double vx,vy,vz;};
void update_vectors(struct position *pos, struct velocity *vel, int n)
{
for (int i=0; i<n; i++)
{
pos[i].px += vel[i].vx;
pos[i].py += vel[i].vy;
pos[i].pz += vel[i].vz;
}
}
even though the Common Initial Sequence guarantee would seem to allow for that.
There are many differences between the two examples, and thus many indications that a compiler could use to allow for the realistic possibility of the first code is passed a struct s2*
, it might accessing a struct s2
, without having to allow for the more dubious possibility that operations upon pos[]
in the second examine might affect elements of vel[]
.
Many implementations seeking to usefully support the Common Initial Sequence rule in useful fashion would be able to handle the first even if no union
type were declared, and I don't know that the authors of the Standard intended that merely adding a union
type declaration should force compilers to allow for the possibility of arbitrary aliasing among common initial sequences of members therein. The most natural intention I can see for mentioning union types would be that compilers which are unable to perceive any of the numerous clues present in the first example could use the presence or absence of any complete union type declaration featuring two types as an indication of whether lvalues of one such type might be used to access another.
Note neither N1570 P6.5p7 nor its predecessors make any effort to describe all cases where quality implementations should behave predictably when given code that uses aggregates. Most such cases are left as Quality of Implementation issues. Since low-quality-but-conforming implementations are allowed to behave nonsensically for almost any reason they see fit, there was no perceived need to complicate the Standard with cases that anyone making a bona fide effort to write a quality implementation would handle whether or not it was required for conformance.