Pretty sure it's safe! Take this program:
typedef struct {
int x;
} Base;
typedef struct {
Base base;
int y;
} Derived;
int f(Base* b, Derived* d) {
b->x = 0;
d->base.x = 1;
return b->x;
}
Notice that if we are accessing the base members of "d", we are still accessing them through a struct of type "Base" (d->base.x). If we compile this with strict aliasing, you can see the output is allowing that the two might alias (while this isn't a proof, it's a strong indication that this is aliasing-correct). 0000000000000000 <f>:
0: c7 07 00 00 00 00 mov DWORD PTR [rdi],0x0
6: c7 06 01 00 00 00 mov DWORD PTR [rsi],0x1
c: 8b 07 mov eax,DWORD PTR [rdi]
e: c3 ret