If an object is defined with static, thread, or automatic storage duration, and it has a character type, either: char
, unsigned char
, or signed char
, it may not be accessed by a non-character type. In the below example a char
array is reinterpreted as the type int
, and the behavior is undefined on every dereference of the int
pointer b
.
int main( void )
{
char a[100];
int* b = ( int* )&a;
*b = 1;
static char c[100];
b = ( int* )&c;
*b = 2;
_Thread_local char d[100];
b = ( int* )&d;
*b = 3;
}
This is undefined because it violates the "effective type" rule, no data object that has an effective type may be accessed through another type that is not a character type. Since the other type here is int
, this is not allowed.
Even if alignment and pointer sizes would be known to fit, this would not exempt from this rule, behavior would still be undefined.
This means in particular that there is no way in standard C to reserve
a buffer object of character type that can be used through pointers
with different types, as you would use a buffer that was received by
malloc
or similar function.
A correct way to achieve the same goal as in the above example would
be to use a union
.
typedef union bufType bufType;
union bufType {
char c[sizeof(int[25])];
int i[25];
};
int main( void )
{
bufType a = { .c = { 0 } }; // reserve a buffer and initialize
int* b = a.i; // no cast necessary
*b = 1;
static bufType a = { .c = { 0 } };
int* b = a.i;
*b = 2;
_Thread_local bufType a = { .c = { 0 } };
int* b = a.i;
*b = 3;
}
Here, the union
ensures that the compiler knows from the start that the buffer could be
accessed through different views. This also has the advantage that now the buffer has a "view" a.i
that already is of type int
and no pointer conversion is needed.