With these definitions:
struct My_Header { uintptr_t bits; }
struct Foo_Type { struct My_Header header; int x; }
struct Foo_Type *foo = ...;
struct Bar_Type { struct My_Header header; float x; }
struct Bar_Type *bar = ...;
Is it correct to say that this C code ("case one"):
foo->header.bits = 1020;
...is actually different semantically from this code ("case two"):
struct My_Header *alias = &foo->header;
alias->bits = 1020;
My understanding is that they should be different:
Case One considers the assignment unable to affect the header in a Bar_Type. It only is seen as being able to influence the header in other Foo_Type instances.
Case Two, by forcing access through a generic aliasing pointer, will cause the optimizer to realize all bets are off for any type which might contain a
struct My_Header
. It would be synchronized with access through any pointer type. (e.g. if you had aFoo_Type
which was pointing to what was actually aBar_Type
, it could access through the header and reliably find out which it had--assuming that's something the header bits could tell you.)
This relies on the optimizer not getting "smart" and making case two back into case one.
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 typeT
, the Standard (deliberately IMHO) refrains from giving blanket permission to access that part of the aggregate's storage using seemingly-unrelated lvalues of typeT
. 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:
Nothing in the Standard would distinguish between the "aliasing" behaviors of the following pairs of functions:
Both of them use an lvalue of type
int
to access the storage associated with objects of typestruct s1
,struct s2
,union U
, andunion U[100]
, even thoughint
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()
ortest2b()
in cases where they would access memberv2.x
of aunion 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 astest1a()
/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 ofunionArr[i].v1
and uses it is likely to access the same storage as other code that usesunionArr[i]
(which is, of course, visibly associated withunionArr[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.The code
bar->header.bits = 1020;
is exactly identical tostruct My_Header *alias = &bar->header; alias->bits = 1020;
.The strict aliasing rule is defined in terms of access to objects through lvalues:
The only things that matter are the type of the lvalue, and the effective type of the object designated by the lvalue. Not whether you stored some intermediate stages of the lvalue's derivation in a pointer variable.
NOTE: The question was edited since the following text was posted. The following text applies to the original question where the space was allocated by
malloc
, not the current question as of August 23.Regarding whether the code is correct or not. Your code is equivalent to Q80
effective_type_9.c
in N2013 rev 1571, which is a survey of existing C implementations with an eye to drafting improved strict aliasing rules.The stumbling block is whether the code
(*bar).header.bits = 1020;
sets the effective type of only theint
bits; or of the entire*bar
. And accordingly, whether reading(*foo).header.bits
reads anint
, or does it read the entire*foo
?Reading only an
int
would not be a strict aliasing violation (it's OK to readint
asint
); but reading aBar_Struct
asFoo_Struct
would be a violation.The authors of this paper consider the write to set the effective type for the entire
*bar
, although they don't give their justification for that, and I do not see any text in the C Standard to support that position.It seems to me there's no definitive answer currently for whether or not your code is correct.
The fact that you have two structures which contain
My_Header
is a red herring and complicates your thinking without bringing anything new to the table. Your problem can be stated and clarified without any struct (other thanMy_Header
ofcourse).The compiler clearly knows which object to modify.
Again the same is true here: with a very rudimentary analysis the compiler knows exactly which object the
alias->bits = 1020;
modifies.The interesting part comes here:
In this function the pointer
p
can alias any object (or sub-object) of typeMy_header
. It really doesn't matter if you have N structures who containMy_header
members or if you have none. Any object of typeMy_Header
could be potentially modified in this function.E.g.
To convince you that the
Foo_Type
andBar_Type
are red herrings and don't matter look at this example for which the analysis is identical to the previous case who doesn't involve neitherFoo_Type
norBar_type
: