The way N1570 p5.6p7 is written, the behavior of code that accesses individual members of structures or unions will only be defined if the accesses are performed using lvalues of character types, or by calling library functions like memcpy
. Even if a struct or union has a member of type T
, the Standard (deliberately IMHO) refrains from giving blanket permission to access that part of the aggregate's storage using seemingly-unrelated lvalues of type T
. Presently, gcc and clang seem to grant blanket permission for accessing structs, but not unions, using lvalues of member type, but N1570 p5.6p7 doesn't require that. It applies the same rules to both kinds of aggregates and their members. Because the Standard doesn't grant blanket permission to access structures using unrelated lvalues of member type, and granting such permission impairs useful optimizations, there's no guarantee gcc and clang will continue this behavior with with unrelated lvalues of member types.
Unfortunately, as can be demonstrated using unions, gcc and clang are very poor at recognizing relationships among lvalues of different types, even when one lvalue is quite visibly derived from the other. Given something like:
struct s1 {short x; short y[3]; long z; };
struct s2 {short x; char y[6]; };
union U { struct s1 v1; struct s2 v2; } unionArr[100];
int i;
Nothing in the Standard would distinguish between the "aliasing" behaviors of the following pairs of functions:
int test1(int i)
{
return unionArr[i].v1.x;
}
int test2a(int j)
{
unionArr[j].v2.x = 1;
}
int test2a(int i)
{
struct s1 *p = &unionArr[i].v1;
return p->x;
}
int test2b(int j)
{
struct s2 *p = &unionArr[j].v2;
p->x = 1;
}
Both of them use an lvalue of type int
to access the storage associated with objects of type struct s1
, struct s2
, union U
, and union U[100]
, even though int
is not listed as an allowable type for accessing any of those.
While it may seem absurd that even the first form would invoke UB, that shouldn't be a problem if one recognizes support for access patterns beyond those explicitly listed in the Standard as a Quality of Implementation issue. According to the published rationale, the authors of the Standard thought compiler writers would to try to produce high-quality implementations, and it was thus not necessary to forbid "conforming" implementations from being of such low quality as to be useless. An implementation could be "conforming" without being able to handle test1a()
or test2b()
in cases where they would access member v2.x
of a union U
, but only in the sense that an implementation could be "conforming" while being incapable of correctly processing anything other than some particular contrived and useless program.
Unfortunately, although I think the authors of the Standard would likely have expected that quality implementations would be able to handle code like test2a()
/test2b()
as well as test1a()
/test1b()
, neither gcc nor clang supports them pattern reliably(*). The stated purpose of the aliasing rules is to avoid forcing compilers to allow for aliasing in cases where there's no evidence of it, and where the possibility of aliasing would be "dubious" [doubtful]. I've seen no evidence that they intended that quality compilers wouldn't recognize that code which takes the address of unionArr[i].v1
and uses it is likely to access the same storage as other code that uses unionArr[i]
(which is, of course, visibly associated with unionArr[i].v2
). The authors of gcc and clang, however, seem to think it's possible for something to be a quality implementation without having to consider such things.
(*) Given e.g.
int test(int i, int j)
{
if (test2a(i))
test2b(j);
return test2a(i);
}
neither gcc nor clang will recognize that if i==j, test2b(j) would access the same storage as test2a(i), even when though both would access the same element of the same array.
My_Header
through a pointer to aMy_Header
, so you haven't really aliased anything. – Blinnierestrict
orvolatile
is coming into a play. – VehementBar_Type
andFoo_Type
are not accessed via union member access, the compiler can behave as though theBar_Type
andFoo_Type
do not overlap in memory even if they do in fact overlap. – Smoothspokenbar->header.bits = ...
, registers didn't sync on reading afoo->header.bits
at the same address). Used alternative assignment method above and it did sync in that optimized build. Quirk/glitch or emergent guarantee of pointer-based access? – Narva