According to the language standard, they are valid and therefore should be allowed.
Now, if you ask how this all works, it's pretty easy.
Pointers are merely memory addresses, usually. And you can have a pointer to any object in memory if the object is represented by an integral number of bytes (each of which has a unique address). I'm saying "integral" here because you can't have a pointer to structure bit fields (at least, you can't have a pointer to those that don't begin on a byte boundary and occupy an integral number of bytes).
So, Ptr2
is just a pointer type of a beforehand known size (if your CPU's address space is from 0 to 4 GB, 32 bits of address are sufficient to address every byte and 32 bits would be the native pointer size on this CPU) and you can allocate space for such a pointer in struct Rec1
and struct Rec2
even if what this pointer points to isn't yet known, which is what we have in the code in question. Ptr1
and Ptr2
are defined as incomplete types, that's the official name for this.
The rationale for implementing such incomplete pointer types in the language is very practical. If you want to create linked lists or trees, their elements or nodes have to point somehow to other elements or nodes they're linked with. You could, in theory, create distinct element/node types for the last element (or a leaf node) and then for the one that points to it and then for the one that points to that one and so on. But this doesn't scale well, if you have more than just a few elements or nodes. What if you want a million of them? Defining a million of nearly identical types is impractical at best. So, the language provides you with a short-cut to avoid this. You could also declare the next
pointers as just void*
pointers, but then you'd need to cast them everywhere to struct Rect1*
or struct Rect2*
and maintaining and debugging of such code is going to be error-prone. So, there, the language gives you a hand.