I'm trying to understand strict aliasing rule as defined in 6.5(p6)
:
If a value is stored into an object having no declared type through an lvalue having a type that is not a character type, then the type of the lvalue becomes the effective type of the object for that access and for subsequent accesses that do not modify the stored value.
and 6.5(p7)
:
An object shall have its stored value accessed only by an lvalue expression that has one of the following types:88)
— a type compatible the effective type of the object
Consider the following example:
struct test_internal_struct_t{
int a;
int b;
};
struct test_struct_t{
struct test_internal_struct_t tis;
};
int main(){
//alocated object, no declared type
struct test_struct_t *test_struct_ptr = malloc(sizeof(*test_struct_ptr));
//object designated by the lvalue has type int
test_struct_ptr->tis.a = 1;
//object designated by the lvalue has type int
test_struct_ptr->tis.b = 2;
//VIOLATION OF STRICT ALIASING RULE???
struct test_internal_struct_t tis = test_struct_ptr->tis;
return 0;
}
The malloc(sizeof(*test_struct_ptr))
has no declared type, since it is allocated, as footnote 87:
87) Allocated objects have no declared type
The objects accessing via test_struct_ptr->tis.a
and test_struct_ptr->tis.b
has effective type to be int
. But the object test_struct_ptr->tis
has no effective type since it is allocated.
QUESTION: Is struct test_internal_struct_t tis = test_struct_ptr->tis;
a violation of strict aliasing? Object designated by test_struct_ptr->tis
has no effective type, but lvalue
has type struct test_internal_struct_t
.
C 2018 6.5 6 defines effective type using the phrase “stored … through an lvalue” but never defines that phrase:
If a value is stored into an object having no declared type through an lvalue having a type that is not a character type, then the type of the lvalue becomes the effective type of the object for that access and for subsequent accesses that do not modify the stored value.
So it is left to readers to interpret. Consider this code:
struct a { int x; };
struct b { int x; };
void copy(int N, struct a *A, struct b *B)
{
for (int n = 0; n < N; ++n)
A[n].x = B[n].x;
}
If the compiler knows that the various objects A[n]
do not overlap the various objects B[n]
, then it can optimize this code by performing a load of several B[n]
in one instruction (such as an AVX or other single-instruction multiple-data [SIMD] instruction) and storing several A[n]
in one instruction. (This may require additional code to handle loop fragment and alignment issues. Those do not concern us here.) If it is possible that some A[n]->x
might refer to the same object as a B[n]->x
for a different value of n
, then the compiler may not use such multi-element loads and stores, as it could change the observable behavior of the program. For example, if the situation were that memory contained ten int
with values from 0 to 9 and B
pointed to the 0 while A
pointed to the 2:
B A 0 1 2 3 4 5 6 7 8 9
Then the loop as written, given N
= 4, must copy the elements one at a time, producing:
0 1 0 1 0 1 6 7 8 9
If the compiler optimized this to four-element loads and stores, it could load 0 1 2 3 and then store 0 1 2 3, producing:
0 1 0 1 2 3 6 7 8 9
However, C tells us that struct a
and struct b
are incompatible, even though they are laid out identically. When types X
and Y
are incompatible, it tells us that an X
is not a Y
. A significant purpose of the type system is to distinguish object types.
Now consider the expression A[n]->x = B[n]->x
. In this:
A[n]
is an lvalue for a struct a
.A[n]
is the left operand of a .
, it is not converted to a value.A[n].x
designates and is an lvalue for the member x
of A[n]
.A[n].x
.So, the direct access to an object that stores a value is solely in the int
that is the member A[n].x
. The lvalue A[n]
appears in the expression, but it is not the lvalue used directly to store a value. What is the effective type of the memory at &A[n]
?
If we interpret this memory as merely an int
, then the only restriction on the object accesses is that all the B[n].x
are int
and all the A[n].x
are int
, so some or all of the A[n].x
may access the same memory as some or all of the B[n].x
, and the compiler is not permitted to make the optimization described above.
This does not serve the purpose of the type system to distinguish struct a
and struct b
, and so it cannot be a correct interpretation. To enable the intended optimization, it must be that the memory stored by A[n].x
contains struct a
objects, and the memory accessed by B[n].x
contains struct b
objects.
Therefore, “stored … through an lvalue” must include expressions where an lvalue is used to derive members of structures but is not itself the final lvalue used for access.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With