答えは、宣言と定義の違いにあります。同じステップで宣言と定義を試みています(typedefを介した新しいタイプの場合)。これらをさまざまなステップに分割して、コンパイラーが事前に何について話しているかを認識できるようにする必要があります。
typedef struct Person Person;
typedef struct People People;
struct Person {
char* name;
int age;
int lefthanded;
People* friends;
};
struct People {
int count;
int max;
Person* data;
};
上部に2つの「空の」typedefが追加されていることに注意してください(宣言)。これは、新しい型Personが型'struct Person'であることをコンパイラーに通知するため、struct Peopleの定義内でそれを見ると、それが何を意味するのかがわかります。
あなたの特定のケースでは、それが定義される前に使用される唯一のタイプであるため、Peopletypdefを事前に宣言するだけで実際に回避することができます。構造体Peopleの定義に入るまでに、タイプPersonはすでに完全に定義されています。したがって、以下も機能しますが、壊れやすいためお勧めしません。
typedef struct People People;
typedef struct {
char* name;
int age;
int lefthanded;
People* friends;
} Person;
struct People {
int count;
int max;
Person* data;
};
構造体定義の順序を入れ替えると(struct PeopleをPersonのtypedefの上に移動する)、再び失敗します。これが脆弱な理由であるため、お勧めしません。
指定されたタイプの構造体へのポインタではなく構造体を含める場合、このトリックは機能しないことに注意してください。したがって、たとえば、次のコンパイルは行われません。
typedef struct Bar Bar;
struct Foo
{
Bar bar;
};
struct Bar
{
int i;
};
上記のコードでは、struct Fooの定義内で使用しようとすると、タイプBarが不完全であるため、コンパイラエラーが発生します。つまり、その時点で構造体バーの定義が表示されていないため、構造体メンバー「バー」に割り当てるスペースがわかりません。
このコードはコンパイルされます:
typedef struct Foo Foo;
typedef struct Bar Bar;
typedef struct FooBar FooBar;
struct Foo
{
Bar *bar;
};
struct Bar
{
Foo *foo;
};
struct FooBar
{
Foo foo;
Bar bar;
FooBar *foobar;
};
これは、FooとBar内の円形ポインターでも機能します。これは、タイプ「Foo」と「Bar」が事前に宣言されているため(ただし、まだ定義されていないため)、コンパイラーがそれらへのポインターを作成できるためです。
FooBarの定義に到達するまでに、FooとBarの両方の大きさを定義して、実際のオブジェクトをそこに含めることができるようにしました。タイプを事前に宣言しているため、タイプFooBarへの自己参照ポインターを含めることもできます。
structFooBarの定義をstructFooまたはBarの定義の上に移動した場合、前の例(不完全な型)と同じ理由でコンパイルされないことに注意してください。