Consider this code example:
#include <stdio.h>
typedef struct A A;
struct A {
int x;
int y;
};
typedef struct B B;
struct B {
int x;
int y;
int z;
};
int main()
{
B b = {1,2,3};
A *ap = (A*)&b;
*ap = (A){100,200}; //a clear http://port70.net/~nsz/c/c11/n1570.html#6.5p7 violation
ap->x = 10; ap->y = 20; //lvalues of types int and int at the right addrresses, ergo correct ?
printf("%d %d %d\n", b.x, b.y, b.z);
}
I used to think that something like casting B* to A* and using A* to manipulate the B* object was a strict aliasing violation. But then I realized the standard really only requires that:
An object shall have its stored value accessed only by an lvalue expression that has one of the following types: 1) a type compatible with the effective type of the object, (...)
and expressions such as ap->x
do have the correct type and address, and the type of ap
shouldn't really matter there (or does it?). This would, in my mind, imply that this type of overlay inheritance is correct as long as the substructure isn't manipulated as a whole.
Is this interpretation flawed or ostensibly at odds with what the authors of the standard intended?
The line with
*ap =
is a strict aliasing violation: an object of typeB
is written using an lvalue expression of typeA
.Supposing that line was not present, and we moved onto
ap->x = 10; ap->y = 20;
. In this case an lvalue of typeint
is used to write objects of typeint
.There is disagreement about whether this is a strict aliasing violation or not. I think that the letter of the Standard says that it is not, but others (including gcc and clang developers) consider
ap->x
as implying that*ap
was accessed. Most agree that the standard's definition of strict aliasing is too vague and needs improvement.Sample code using your struct definitions:
For me this outputs
214
at-O2
, and2
at-O3
, with gcc. The generated assembly on godbolt for gcc 6.3 was:which shows that the compiler has rearranged the function to:
and therefore the compiler must be considering that
ap->x
may not aliasbp->x
.When C89 was written, it would have been impractical for a compiler to uphold the Common Initial Sequence guarantees for unions without also upholding them for struct pointers. By contrast, specifying the CIS guarantees for struct pointers would not imply that unions would exhibit similar behavior if their address was not taken. Given that the CIS guarantees have been applicable to struct pointers since January 1974--even before the
union
keyword was added to the language--and a lot of code had for years relied upon such behavior in circumstances which could not plausibly involve objects ofunion
type, and that the authors of the C89 were more interested in making the Standard concise than in making it "language-lawyer-proof", I would suggest that C89's specification of CIS rule in terms of unions rather than struct pointers was almost certainly motivated by a desire to avoid redundancy, rather than a desire to allow compilers the freedom to go out of their way to violate 15+ years of precedent in applying CIS guarantees to structure pointers.The authors of C99 recognized that in some cases applying the CIS rule to structure pointers might impair what would otherwise be useful optimization, and specified that if a pointer of one structure type is used to inspect a CIS of member of another, the CIS guarantee won't hold unless a definition of a complete union type containing both structures is in scope. Thus, for your example to be compatible with C99, it would need to contain a definition of a
union
type containing both of your structures. This rule appears to have been motivated by a desire to allow compilers to limit application of the CIS to cases where they would have reason to expect that two types might be used in related fashion, and to allow code to indicate that types are related without having to add a new language construct for that purpose.The authors of gcc seem to think that because it would be unusual for a code to receive a pointer to a member of a union and then want to access another member of a union, the mere visibility of a complete union type definition should not be sufficient to force a compiler to uphold CIS guarantees, even though most uses of the CIS had always revolved around structure pointers rather than unions. Consequently, the authors of gcc refuse to support constructs like yours even in cases where the C99 Standard would require it.