The C aliasing rules
A lot of people are confused by the pointer aliasing rules introduced by C99. (Sometimes called “strict aliasing”.) First, what is aliasing? When two pointers refer to the same location, they’re called “aliases”.
void fcn(int *p) { int *q = p; ... }
Here p
and q
are aliases. The general rule is that pointers to the same type are allowed to alias, and pointers to different types are not. (Differences like signed/unsigned and const/non-const aren’t considered significant here– they’re all in the same “aliasing class”.)
void fcn(float *p) { int *q = (int *) p; ... }
This is illegal, because p
and q
are aliases, but one points to float
while the other points to int
. In practical terms, the optimizer will reorder accesses to *p
and *q
across each other, or optimize out writing/reading the value back into/from memory, because they’re in different alias classes, and you’ll get mysterious bugs.
There are two exceptions to this rule: void *
and char *
. Pointers of these two types are allowed to alias any kind of pointer. void *
can be assigned from and to any pointer without a cast, but you’re only allowed to assign it to a type which reflects the original pointer (or char *
).
void intfcn(void *p) { int *q = p; ... }
void floatfcn(void *p) { float *q = p; ... }
void fcn(void) {
int i;
intfcn(&i); /* OK: int * -> void * -> int * */
floatfnc(&i); /* Bad: int * -> void * -> float * */
}
This style is used a lot in callbacks, where it’ll look something like:
void callsoon(void (*fcn)(void *), void *arg); /* Call fcn(arg) in one second */
void fcn(void) {
static int i = 1;
static float f = 0.1;
callsoon(intfcn, &i);
callsoon(floatfcn, &f);
}
The compiler is still assuming your int *
and float *
pointers won’t alias here: It’s up to you to ensure that the void *
which got a float *
doesn’t get turned into an int *
and vice-versa. The void *
is telling the compiler “I can ensure non-aliasing on my own, without you checking”. You should not use it to silence aliasing warnings, because you get those warnings in situations where the compiler is going to re-order accesses, and the void *
gives it free license to do so silently!
What do you do when you really need to alias across types? First, don’t do a straight pointer typecast or go through a void *
typecast for the reasons above:
void fcn(float *p) { int *q = (void *)p; ... }
void fcn(float f) { int i = *(int *)&f; ... }
void fcn(float f) { int i = *(int *)(void *)&f; ... } /* All illegal aliasing */
Another common but illegal technique is to go through a union
:
union { int i; float f; } x; x.f = f; i = x.i; /* Illegal: unions can only be read from the last type assigned to them */
So, what do you do? One of the most common use cases is for transferring byte representations, and memcpy()
works fine for this (any modern compiler will optimize it into stores).
void fcn(float *p) { int i; memcpy(&i, p, sizeof(i)); ... } /* Safe if sizeof(int) == sizeof(float) */
void fcn(float f) { int i; memcpy(&i, &f, sizeof(i)); ... } /* Ditto */
void fcn(float *p) { int *q; memcpy(&q, &p, sizeof(q)); ... } /* Illegal: just a fancy way of writing q = (int *)p */
If you need fine-grained access, this is where char *
comes in. It’s allowed to alias any other pointer, so the compiler can’t re-order pointer accesses across char *
accesses unless it can ensure non-aliasing by other means.
void fcn(float *p) { char *q = (char *)p; ... } /* Safe */
Note that just going though a char *
on your way to an illegal alias doesn’t make it legal:
void fcn(float *p) {
char *q = (char *)p; /* q now validly aliases p */
int *r = (int *)q; /* r now validly aliases q, but p and r are still in different alias classes and *p and *r can be re-ordered across each other */
...
}
void fcn(float *p) { int *q = (int *)(char *)p; ... } /* Illegal aliasing of p and q, same as q = (int *)p */
char *
is safe, as long as you do your references through it, as char
s (bytes), but just having a char *
there doesn’t make non-char *
references legal.