> I understand the upcast (which is certainly legal but it forces the casting code to know the depth of the inheritance hierarchy - as in &derived->base1.base2)
If this is inconvenient, just casting directly to Base pointer is also legal.
> but what's the argument making the downcast back to Derived legal C?
The justification comes from this part of the C standard (C99 6.7.2.1 p13):
Within a structure object, the non-bit-field
members and the units in which bit-fields reside
have addresses that increase in the order in which
they are declared. A pointer to a structure
object, suitably converted, points to its initial
member (or if that member is a bit-field, then to
the unit in which it resides), and vice versa.
There may be unnamed padding within a structure
object, but not at its beginning.
It follows that:
Derived *d = GetDerived();
// This is legal: a pointer to Derived, suitably converted,
// points to its initial member "base":
Base *base = (Base*)d;
// This is also legal: a pointer to Derived.base, the initial
// member of Derived, suitably converted, points to Derived.
Derived *d2 = (Derived*)base;