Why?
In order to understand why, we need to think like a compiler. Let's do that while analysing main.c
line by line. What would a compiler do?
#include <datastruct1.h>
: Put "main.c" aside (push to the stack of files being processed) and switch to "datastruct1.h"
#ifndef DATA_STRUCT_ONE
: hmm, this is not defined, let's continue.
#define DATA_STRUCT_ONE
: OK, defined!
#include <datastruct2.h>
: Put "datastruct1.h" aside and switch to "datastruct2.h"
#ifndef DATA_STRUCT_TWO
: hmm, this is not defined, let's continue.
#define DATA_STRUCT_TWO
: OK, defined!
#include <datastruct1.h>
: Put "datastruct2.h" aside and switch to "datastruct1.h"
#ifndef DATA_STRUCT_ONE
: this is now defined, so go straigh to #endif
.
(end of "datastruct1.h")
: close "datastruct1.h" and pop current file from the stack of filles. What were I doing? Ahh, "datastruct2.h". Let's continue from the place where we left.
typedef struct DataStructTwo_t
ok, starting a struct definition
DataStructOne* one;
Wait, what is DataStructOne
? We have not seen it? (looking up the list of processed lines) Nope, no DataStructOne
in sight. Panic!
What happened? In order to compile "datastruct2.h", the compiler needs "datastruct1.h", but the #include
guards in it "datastruct1.h" prevent its content from being actually included where it's needed.
The situation is symmetrical, so if we switch the order of #include
directives in "main.c", we get the same result with the roles of the two files reversed. We cannot remove the guards either, because that would cause an infinite chain of file inclusions.
It appears that we need "datastruct2.h" to appear before "datastruct1.h" and we need "datastruct1.h" to appear before "datastruct2.h". This does not seem possible.
What?
The situation where file A #include
s file B which in turn #include
s file A is clearly unacceptable. We need to break the vicious cycle.
Fortunately C and C++ have forward declarations. We can use this language feature to rewrite our header files:
#ifndef DATA_STRUCT_ONE
#define DATA_STRUCT_ONE
// No, do not #include <datastruct2.h>
struct DataStructTwo_t; // this is forward declaration
typedef struct DataStructOne_t
{
struct DataStructTwo_t* two;
} DataStructOne;
#endif
In this case we can rewrite "datastruct2.h" the same way, eliminating its dependency on "datastruct1.h", breaking the cycle in two places (strictly speaking, this is not needed, but less dependencies is always good). Alas. this is not always the case. Often there is only one way to introduce a forward declaration and break the cycle. For ecample, if, instead of
DataStructOne* one;
we had
DataStructOne one; // no pointer
then a forward declaration would not work in this place.
What if I cannot use a forward declaration anywhare?
Then you have a design problem. For example, if instead of both DataStructOne* one;
and DataStructTwo* two;
you had DataStructOne one;
and DataStructTwo two;
, then this data structure is not realisable in C or C++. You need to change one of the fields to be a pointer (in C++: a smart pointer), or eliminate it altogether.