Given the code:
struct s1 {unsigned short x;};
struct s2 {unsigned short x;};
union s1s2 { struct s1 v1; struct s2 v2; };
static int read_s1x(struct s1 *p) { return p->x; }
static void write_s2x(struct s2 *p, int v) { p->x=v;}
int test(union s1s2 *p1, union s1s2 *p2, union s1s2 *p3)
{
if (read_s1x(&p1->v1))
{
unsigned short temp;
temp = p3->v1.x;
p3->v2.x = temp;
write_s2x(&p2->v2,1234);
temp = p3->v2.x;
p3->v1.x = temp;
}
return read_s1x(&p1->v1);
}
int test2(int x)
{
union s1s2 q[2];
q->v1.x = 4321;
return test(q,q+x,q+x);
}
#include <stdio.h>
int main(void)
{
printf("%d\n",test2(0));
}
There exists one union object in the entire program--q. Its active member is set to v1, and then to v2, and then to v1 again. Code only uses the address-of operator on q.v1, or the resulting pointer, when that member is active, and likewise q.v2. Since p1, p2, and p3 are all the same type, it should be perfectly legal to use p3->v1 to access p1->v1, and p3->v2 to access p2->v2.
I don't see anything that would justify a compiler failing to output 1234, but many compilers including clang and gcc generate code that outputs 4321. I think what's going on is that they decide that the operations on p3 won't actually change the contents of any bits in memory, they can just be ignored altogether, but I don't see anything in the Standard that would justify ignoring the fact that p3 is used to copy data from p1->v1 to p2->v2 and vice versa.
Is there anything in the Standard that would justify such behavior, or are compilers simply not following it?
I believe that your code is conformant, and there is a flaw with the -fstrict-aliasing mode of GCC and Clang.
I cannot find the right part of the C standard, but the same problem happens when compiling your code in C++ mode for me, and I did find the relevant passages of the C++ Standard.
In the C++ standard, [class.union]/5 defines what happens when operator = is used on a union access expression. The C++ Standard states that when a union is involved in the member access expression of the built-in operator =, the active member of the union is changed to the member involved in the expression (if the type has a trivial constructor, but because this is C code, it does have a trivial constructor).
Note that write_s2x cannot change the active member of the union, because a union is not involved in the assignment expression. Your code does not assume that this happens, so it's OK.
Even if I use placement new to explicitly change which union member is active, which ought to be a hint to the compiler that the active member changed, GCC still generates code that outputs 4321.
This looks like a bug with GCC and Clang assuming that the switching of active union member cannot happen here, because they fail to recognize the possibility of p1, p2 and p3 all pointing to the same object.
GCC and Clang (and pretty much every other compiler) support an extension to C/C++ where you can read an inactive member of a union (getting whatever potentially garbage value as a result), but only if you do this access in a member access expression involving the union. If v1 were not the active member, read_s1x would not be defined behavior under this implementation-specific rule, because the union is not within the member access expression. But because v1 is the active member, that shouldn't matter.
This is a complicated case, and I hope that my analysis is correct, as someone who isn't a compiler maintainer or a member of one of the committees.
With a strict interpretation of the standard, this code might be not conforming. Let's focus on the text of the well-known §6.5p7:
An object shall have its stored value accessed only by an lvalue expression that has one of
the following types:
— a type compatible with the effective type of the object,
— a qualified version of a type compatible with the effective type of the object,
— a type that is the signed or unsigned type corresponding to the effective type of the
object,
— a type that is the signed or unsigned type corresponding to a qualified version of the
effective type of the object,
— an aggregate or union type that includes one of the aforementioned types among its
members (including, recursively, a member of a subaggregate or contained union), or
— a character type.
(emphasis mine)
Your functions read_s1x() and write_s2x() do the opposite of what I marked bold above in the context of your whole code. With just this paragraph, you could conclude that it's not allowed: A pointer to union s1s2 would be allowed to alias a pointer to struct s1, but not vice versa.
This interpretation of course would mean that the code must work as intended if you "inline" these functions manually in your test(). This is indeed the case here with gcc 6.2 for i686-w64-mingw32.
Adding two arguments in favor of the strict interpretation presented above:
While it's always allowed to alias any pointer with char *, a character array can't be aliased by any other type.
Considering the (here unrelated) §6.5.2.3p6:
One special guarantee is made in order to simplify the use of unions: if a union contains
several structures that share a common initial sequence (see below), and if the union
object currently contains one of these structures, it is permitted to inspect the common
initial part of any of them anywhere that a declaration of the completed type of the union
is visible.
(again emphasis mine) -- the typical interpretation is that being visible means directly in the scope of the function in question, not "somewhere in the translation unit" ... so this guarantee doesn't include a function that takes a pointer to one of the structs that's a member of the union.
I didn't read the standard, but playing with pointers in a strict-aliasing mode (ie, using -fstrict-alising) is dangerous. See the gcc online doc:
Pay special attention to code like this:
union a_union {
int i;
double d;
};
int f() {
union a_union t;
t.d = 3.0;
return t.i;
}
The practice of reading from a different union member than the one most recently written to (called type-punning) is common. Even with -fstrict-aliasing, type-punning is allowed, provided the memory is accessed through the union type. So, the code above works as expected. See Structures unions enumerations and bit-fields implementation. However, this code might not:
int f() {
union a_union t;
int* ip;
t.d = 3.0;
ip = &t.i;
return *ip;
}
Similarly, access by taking the address, casting the resulting pointer and dereferencing the result has undefined behavior, even if the cast uses a union type, e.g.:
int f() {
double d = 3.0;
return ((union a_union *) &d)->i;
}
The -fstrict-aliasing option is enabled at levels -O2, -O3, -Os.
Found anything similar in the second example huh?
It is not about conforming or not conforming - it one of the optimisation "traps". All of your data structures have been optimised out and you pass the same pointer to optimised out data so the the execution tree is reduced to simple printf of the value.
sub rsp, 8
mov esi, 4321
mov edi, OFFSET FLAT:.LC0
xor eax, eax
call printf
xor eax, eax
add rsp, 8
ret
to change it you need to make this "transfer" function to be side effect prone and force the real assignments. It will force optimizer to not reduce those nodes in the execution tree:
int test(union s1s2 *p1, union s1s2 *p2, volatile union s1s2 *p3)
/* ....*/
main:
sub rsp, 8
mov esi, 1234
mov edi, OFFSET FLAT:.LC0
xor eax, eax
call printf
xor eax, eax
add rsp, 8
ret
it is quite trivial test just artificially made a bit more complicated.
Related
I'm reading ISO/IEC 9899:2017 recently,following is the source link
https://web.archive.org/web/20181230041359if_/http://www.open-std.org/jtc1/sc22/wg14/www/abq/c17_updated_proposed_fdis.pdf
And what makes me confused is the following statements, because the standard said it is not a valid fragment (because the union type is not visible within function f).
But I got the correct return value when I run this code.
#include <stdio.h>
struct t1 { int m; };
struct t2 { int m; };
int f(struct t1 *p1, struct t2 *p2){
if (p1->m < 0)
p2->m = -p2->m;
return p1->m;
}
int g(){
union U{
struct t1 s1;
struct t2 s2;
} u = {-1};
return f(&u.s1, &u.s2);
}
int main(){
int i = g();
printf("%d\n",i);
}
So my question is:
Is that valid parsing a pointer of union's member which is a structure defined in file scope to another function where union type is not visible to?
But I got the correct return value when I run this code.
A given compiler accepting the code and the compiled program exhibiting the expected observable behavior are not reliable indicators of the code being correct with respect to the language specification. Nor, therefore, are they reliable predictors of whether a different compiler will accept the code or produce a program that exhibits the expected observable behavior.
Conforming compilers emit diagnostics about certain classes of errors, but they are not required to diagnose all errors, and there are some classes of errors that could not be detected at compile time even if the compiler wanted to do. Generally speaking, compilation and / or execution of incorrect code produces undefined behavior, which many times does not involve any error messages, and which sometimes is even the behavior that was expected. Bottom line: that a program produces the correct or expected result does not prove that the program is correct.
As for the main question,
what makes me confused is the following statements, because the standard said it is not a valid fragment (because the union type is not visible within function f).
[...]
So my question is: Is that valid [passing] a pointer of union's member which is a structure defined in file scope to another function where union type is not visible to?
You have misunderstood the nature of the problem. It is not inherent in passing a pointer to a member of a union. Rather, it has to do with accessing more than one member of the same union object.
The general rule arises from
The value of at most one of the
members can be stored in a union object at any time
(C17 6.7.2.1/16)
and the so-called strict aliasing rule, paragraph 6.5/7:
An object shall have its stored value accessed only by an lvalue expression that has one of the following types:
[a type compatible with the objects effective type, plus / minus qualification, or the corresponding signed / unsigned type of one of the above, or]
an aggregate or union type that includes one of the aforementioned types among its members (including, recursively, a member of a subaggregate or contained union), or
a character type.
The storage for every member of a given union object overlaps the storage of all the others (which is why the union can hold only one at a time), therefore with ...
union U{
struct t1 s1;
struct t2 s2;
} u = {-1};
... &u.s1 and &u.s2 point to the same storage. With the given initialization, its effective type is struct s1, and it is the initial part of a possibly larger block of storage whose effective type is union U.
Structure types with different tags are never compatible with each other, so the strict aliasing rule would be violated by g() accessing that initial value via the pointer &u.s2, except that the specification carves out a special case:
One special guarantee is made in order to simplify the use of unions: if a union contains several structures that share a common initial sequence (see below), and if the union object currently contains one of these structures, it is permitted to inspect the common initial part of any of them anywhere that a declaration of the completed type of the union is visible.
(C17 6.5.2.3/6)
This is precisely what the example you're looking at is about. Because struct t1 and struct t2 have a common initial sequence consisting of their respective members m, and because the union object in function g() initially does contain a value for its member s1, of type struct t1, it is permitted in g() to access u.s2.m, including indirectly via &u.s2, even though u.s2 is not the member that currently contains a value.
However, 6.5.2.3/6 does not apply in function f(), because type union U is not visible there. Therefore, although it's fine for f() to access p1->m, it produces UB for it to attempt to access p2->m. This is the claim you inquired about.
This question already has an answer here:
Struct Extension in C
(1 answer)
Closed 2 years ago.
Pointer aliasing in C is normally undefined behavior (because of strict aliasing), but C11 standard seems allow aliasing a pointer to struct and a pointer to the first member of the struct
C11 6.7.2.1 (15)...A pointer to a structure object... points to its initial member... and vice versa...
So does the following code contain undefined behavior?
struct Foo {
int x;
int y;
};
// does foe return always 100?
int foe() {
struct Foo foo = { .x = 10, .y = 20 }, *pfoo = &foo;
int *px = (int*)pfoo; *px = 100;
return pfoo->x;
}
This code is correct. All versions of Standard C and C++ allow this , although the wording varies.
There's no strict aliasing issue because you access an object of type int via an lvalue of type int. The strict aliasing rule may apply when the lvalue doing the access has a different type to the object stored at the memory location .
The text you quoted covers that the pointer cast actually points to the int object.
The way the Standard is written, an lvalue of a structure or union type may be used to access an object of member type, but there is no provision that would allow an arbitrary lvalue of struct or union's member type to access an object of the struct or union type. Because it would of course be absurd to say that code couldn't use a struct or union member lvalue (which would of course have that member's type) to access a struct or union, all compilers have supported some common access patterns. Because compilers allow such accesses under different circumstances, however, the Standard treats all support for such accesses as a Quality of Implementation issue rather than trying to specify exactly when such support is required.
The approach most consistent with the Standard's wording, and which would allow the most useful optimizations, while also supporting most code that would need to perform type punning or other techniques, would be to say that for purposes of N1570 6.5p7, a pointer which is visibly derived from a pointer or lvalue of a given type may be used within the context of such derivation to access things that would (for purposes of 6.5p7) be accessible using an lvalue of that type. Under such an approach, given a piece of code like:
struct foo { int index,len; int *dat; };
void test1(struct foo *p)
{
int *pp = &foo->len;
*pp = 4;
}
void test2(struct foo *p, int dat)
{
if (p->index < p->len)
{
p->dat[p->index] = dat;
p->index++;
}
}
should recognize that within test1, an access to *pp may access the struct foo object *p, since pp is visibly formed from p. On the other hand, the compiler would not be required to accommodate within test2 the possibility that an object of type struct foo, nor members thereof such as p->index, might be modified through the pointer p->dat, because nothing within test2 would cause the address of a struct foo or any portion thereof to be stored in p->dat.
Clang and gcc, however, instead opt for a different approach, behaving as though 6.5p7 allows struct members to be accessed via arbitrary pointers of their types, but union members can't be accessed via pointers at all, excluding the pointer arithmetic implied by bracketed array expressions. Given union { uint16_t h[4]; uint32_t w[2];} u; clang and gcc will recognize that an access to u.h[i] might interact with u.w[j], but will not recognize that *(u.h+i) might interact with *(u.w+j) even though the Standard defines the meaning of the former expressions with brackets as being equivalent to the latter forms.
Given that compilers consistently handle all of these constructs usefully when type-based aliasing is disabled. The Standard, however, doesn't impose any requirements even in many common cases, and clang and gcc make no promises about behavior of constructs not mandated by the Standard, even if all versions to date have handled such constructs usefully. Thus, I would not recommend relying upon clang or gcc to usefully process anything that involves accessing storage as different types at different times except when using -fno-strict-aliasing, and their wackiness isn't an issue when using that option, so I'd recommend simply using that option unless or until clang and gcc adopt a better defined abstraction.
When I read ISO/IEC 9899:1999 (see:6.5.2.3), I saw an example like this (emphasis mine) :
The following is not a valid fragment (because the union type is not visible within function f):
struct t1 { int m; };
struct t2 { int m; };
int f(struct t1 * p1, struct t2 * p2)
{
if (p1->m < 0)
p2->m = -p2->m;
return p1->m;
}
int g()
{
union {
struct t1 s1;
struct t2 s2;
} u;
/* ... */
return f(&u.s1, &u.s2);
}
I found no errors and warnings when I tested.
My question is: Why is this fragment invalid?
The example attempts to illustrate the paragraph beforehand1 (emphasis mine):
6.5.2.3 ¶6
One special guarantee is made in order to simplify the use of unions:
if a union contains several structures that share a common initial
sequence (see below), and if the union object currently contains one
of these structures, it is permitted to inspect the common initial
part of any of them anywhere that a declaration of the completed type
of the union is visible. Two structures share a common initial
sequence if corresponding members have compatible types (and, for
bit-fields, the same widths) for a sequence of one or more initial
members.
Since f is declared before g, and furthermore the unnamed union type is local to g, there is no questioning the union type isn't visible in f.
The example doesn't show how u is initialized, but assuming the last written to member is u.s2.m, the function has undefined behavior because it inspects p1->m without the common initial sequence guarantee being in effect.
Same goes the other way, if it's u.s1.m that was last written to before the function call, than accessing p2->m is undefined behavior.
Note that f itself is not invalid. It's a perfectly reasonable function definition. The undefined behavior stems from passing into it &u.s1 and &u.s2 as arguments. That is what's causing undefined behavior.
1 - I'm quoting n1570, the C11 standard draft. But the specification should be the same, subject only to moving a paragraph or two up/down.
Here is the strict aliasing rule in action: one assumption made by the C (or C++) compiler, is that dereferencing pointers to objects of different types will never refer to the same memory location (i.e. alias each other.)
This function
int f(struct t1* p1, struct t2* p2);
assumes that p1 != p2 because they formally point to different types. As a result the optimizatier may assume that p2->m = -p2->m; have no effect on p1->m; it can first read the value of p1->m to a register, compare it with 0, if it compare less than 0, then do p2->m = -p2->m; and finally return the register value unchanged!
The union here is the only way to make p1 == p2 on binary level because all union member have the same address.
Another example:
struct t1 { int m; };
struct t2 { int m; };
int f(struct t1* p1, struct t2* p2)
{
if (p1->m < 0) p2->m = -p2->m;
return p1->m;
}
int g()
{
union {
struct t1 s1;
struct t2 s2;
} u;
u.s1.m = -1;
return f(&u.s1, &u.s2);
}
What must g return? +1 according to common sense (we change -1 to +1 in f). But if we look at gcc's generate assembly with -O1 optimization
f:
cmp DWORD PTR [rdi], 0
js .L3
.L2:
mov eax, DWORD PTR [rdi]
ret
.L3:
neg DWORD PTR [rsi]
jmp .L2
g:
mov eax, 1
ret
So far all is as excepted. But when we try it with -O2
f:
mov eax, DWORD PTR [rdi]
test eax, eax
js .L4
ret
.L4:
neg DWORD PTR [rsi]
ret
g:
mov eax, -1
ret
The return value is now a hardcoded -1
This is because f at the beginning caches the value of p1->m in the eax register (mov eax, DWORD PTR [rdi]) and does not reread it after p2->m = -p2->m; (neg DWORD PTR [rsi]) - it returns eax unchanged.
union here used only for
all non-static data members of a union object have the same address. as result &u.s1 == &u.s2.
is somebody not understand assembler code, can show in c/c++ how strict aliasing affect f code:
int f(struct t1* p1, struct t2* p2)
{
int a = p1->m;
if (a < 0) p2->m = -p2->m;
return a;
}
compiler cache p1->m value in local var a (actually in register of course) and return it , despite p2->m = -p2->m; change p1->m. but compiler assume that p1 memory not affected, because it assume that p2 point to another memory which not overlap with p1
so with different compilers and different optimization level the same source code can return different values (-1 or +1). so and undefined behavior as is
One of the major purposes of the Common Initial Sequence rule is to allow functions to operate on many similar structures interchangeably. Requiring that compilers presume that any function which acts upon a structure might change the corresponding member in any other structure that shares a common initial sequence, however, would have impaired useful optimizations.
Although most code which relies upon the Common Initial Sequence guarantees makes use of a few easily recognizable patterns, e.g.
struct genericFoo {int size; short mode; };
struct fancyFoo {int size; short mode, biz, boz, baz; };
struct bigFoo {int size; short mode; char payload[5000]; };
union anyKindOfFoo {struct genericFoo genericFoo;
struct fancyFoo fancyFoo;
struct bigFoo bigFoo;};
...
if (readSharedMemberOfGenericFoo( myUnion->genericFoo ))
accessThingAsFancyFoo( myUnion->fancyFoo );
return readSharedMemberOfGenericFoo( myUnion->genericFoo );
revisiting the union between calls to functions that act on different union members, the authors of the Standard specified that visibility of the union type within the called function should be the determining factor for whether functions should recognize the possibility that an access to e.g. field mode of a FancyFoo might affect field mode of a genericFoo. The requirement to have a union containing all types of structures whose address might be passed to readSharedMemberOfGeneric in the same compilation unit as that function makes the Common Initial Sequence rule less useful than it would otherwise be, but would make at least allow some patterns like the above usable.
The authors of gcc and clang thought that treating union declarations as an indication that the types involved might be involved in constructs like the above would be an impractical impediment to optimization, however, and figured that since the Standard doesn't require them to support such constructs via other means, they'll simply not support them at all. Consequently, the real requirement for code that would need to exploit the Common Initial Sequence guarantees in any meaningful fashion is not to ensure that a union type declaration is visible, but to ensure that clang and gcc are invoked with the -fno-strict-aliasing flag. Also including a visible union declaration when practical wouldn't hurt, but it is neither necessary nor sufficient to ensure correct behavior from gcc and clang.
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 type B is written using an lvalue expression of type A.
Supposing that line was not present, and we moved onto ap->x = 10; ap->y = 20;. In this case an lvalue of type int is used to write objects of type int.
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:
void f(A* ap, B* bp)
{
ap->x = 213;
++bp->x;
ap->x = 213;
++bp->x;
}
int main()
{
B b = { 0 };
f( (A *)&b, &b );
printf("%d\n", b.x);
}
For me this outputs 214 at -O2, and 2 at -O3 , with gcc.
The generated assembly on godbolt for gcc 6.3 was:
f:
movl (%rsi), %eax
movl $213, (%rdi)
addl $2, %eax
movl %eax, (%rsi)
ret
which shows that the compiler has rearranged the function to:
int temp = bp->x + 2;
ap->x = 213;
bp->x = temp;
and therefore the compiler must be considering that ap->x may not alias bp->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 of union 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.
The following example is given in the C11 standard, 6.5.2.3
The following is not a valid fragment (because the union type is not
visible within function f):
struct t1 { int m; };
struct t2 { int m; };
int f(struct t1 *p1, struct t2 *p2)
{
if (p1->m < 0)
p2->m = -p2->m;
return p1->m;
}
int g()
{
union {
struct t1 s1;
struct t2 s2;
} u;
/* ... */
return f(&u.s1, &u.s2);
}
Why does it matter that the union type is visible to the function f?
In reading through the relevant section a couple times, I could not see anything in the containing section disallowing this.
It matters because of 6.5.2.3 paragraph 6 (emphasis added):
One special guarantee is made in order to simplify the use of unions:
if a union contains several structures that share a common initial
sequence (see below), and if the union object currently contains one
of these structures, it is permitted to inspect the common initial
part of any of them anywhere that a declaration of the completed type
of the union is visible. Two structures share a common initial
sequence if corresponding members have compatible types (and, for
bit-fields, the same widths) for a sequence of one or more initial
members.
It's not an error that requires a diagnostic (a syntax error or constraint violation), but the behavior is undefined because the m members of the struct t1 and struct t2 objects occupy the same storage, but because struct t1 and struct t2 are different types the compiler is permitted to assume that they don't -- specifically that changes to p1->m won't affect the value of p2->m. The compiler could, for example, save the value of p1->m in a register on first access, and then not reload it from memory on the second access.
Note: This answer doesn't directly answer your question but I think it is relevant and is too big to go in comments.
I think the example in the code is actually correct. It's true that the union common initial sequence rule doesn't apply; but nor is there any other rule which would make this code incorrect.
The purpose of the common initial sequence rule is to guarantee the same layout of the structs. However that is not even an issue here, as the structs only contain a single int, and structs are not permitted to have initial padding.
Note that , as discussed here, sections in ISO/IEC documents titled Note or Example are "non-normative" which means they do not actually form a part of the specification.
It has been suggested that this code violates the strict aliasing rule. Here is the rule, from C11 6.5/7:
An object shall have its stored value accessed only by an lvalue expression that has one of the following types:
a type compatible with the effective type of the object,
[...]
In the example, the object being accessed (denoted by p2->m or p1->m) have type int. The lvalue expressions p1->m and p2->m have type int. Since int is compatible with int, there is no violation.
It's true that p2->m means (*p2).m, however this expression does not access *p2. It only accesses the m.
Either of the following would be undefined:
*p1 = *(struct t1 *)p2; // strict aliasing: struct t2 not compatible with struct t1
p2->m = p1->m++; // object modified twice without sequence point
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.