struct B; is a structure declaration. There can be any number of declarations of the same object. For object types that have several possible declarations, declarations must be compatible - for example, you can have several prototypes for the same function, but they need to use the same arguments and return data types. For a structure, there is only one way to declare it without defining it, and it simply says "there is a structure called B ". So it's ok to repeat struct B; as many times as you want.
struct B{int a;}; is a definition of structure. There can be only one definition of a given object. Some uses of the property require a preliminary definition, others require only a preliminary announcement. For example, you can define a variable whose type is a pointer to an undefined structure:
struct S; struct S *p = NULL;
But you cannot define a variable whose type is an undefined structure:
struct S; struct S s = {0};
The reason for this difference is that a structure that is not yet defined is an incomplete type β a type for which the compiler has no size. This is not a problem for defining a pointer, but the problem is defining an object of an incomplete type: the compiler does not know how much memory is required.
At this point, you may wonder why struct B b; OK. In the end, struct B was not even declared, much less defined.
The first part, why this is so, means that struct B b; passes structure B along the way. Therefore, by the time struct B needs to do something with B , the structure has just been declared.
The second part, why is it normal that struct B b; not an exact definition. If this were a definition, then the compiler would have to know how much space to reserve, and the incomplete type is not in order. This is what happens if the variable was defined in the function:
void f(void) { struct B b;
But when struct B b; located at the top level of the file, this is a preliminary definition. A preliminary definition is an announcement; for this, the structure should not be defined. When the compiler gets to the end of the file, any definition that does not have a correct definition and that requires a definition becomes a definition. At this stage, the structure should be defined. In your sample program, B has a preliminary definition, and that definition becomes the correct definition at the end of the file.
It is normal to define a structure if a variable is not used, because it is normal to declare a variable with an incomplete type if the variable is not used.
struct B b;
But if a variable is used in a way that requires a full type, then the structure must be defined at this point.
struct B b; void f(void) { sizeof(b);