解决循环typedef依赖?

39

在这些结构体的 typedef 中,解决以下循环依赖的最佳方法是什么?
注意C语言标签 - 我正在寻找标准gcc C的解决方案。

typedef struct {
    char* name;
    int age;
    int lefthanded;
    People* friends;
} Person;

typedef struct {
    int count;
    int max;
    Person* data;
} People;
6个回答

49
答案:答案在声明和定义之间的区别上。您正在尝试在同一步骤中声明和定义(例如通过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;
};
请注意顶部两个“空白”typedef的添加(声明)。这告诉编译器新类型Person属于“struct Person”类型,因此在结构体People的定义中遇到它时,编译器知道它的含义。
在您的特定情况下,您实际上只需要预先声明People typedef就可以了,因为在其定义之前,它是唯一使用的类型。当您进入结构体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移动到typedef Person之上),它将再次失败。这就是使其脆弱并且不建议使用的原因。
请注意,如果您包括指定类型的结构体而不是指向它的指针,则此技巧将无法起作用。因此,例如以下内容将无法编译
typedef struct Bar Bar;

struct Foo
{
    Bar bar;
};

struct Bar
{
    int i;
};

由于在定义结构体Foo时尝试在其中使用不完整的类型Bar,以上代码将导致编译错误。换句话说,此时它还没有看到struct bar的定义,因此不知道为结构成员'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的自引用指针,因为我们已经预先声明了该类型。

请注意,如果将结构体FooBar的定义移到结构体Foo或Bar的定义之前,由于类型不完整,它将无法编译。


35

提前声明其中一个结构体:


struct people;

typedef struct {
  /* same as before */
  struct people* friends;
} Person;

typedef struct people {
  /* same as before */
} People;

2
有人应该提到,你需要写 typedef struct people { .... } People;。所以,这与之前不完全相同(在我看来,此外明确的标记名称也是一个好主意)。 - Johannes Schaub - litb
你说得对,我太懒了,没有回去编辑答案,现在会去做的。 - Nikolai Fetissov
1
在结构体Person中,我们不能声明struct people friends而不是指针吗?这对我来说会产生错误。例如:struct people; typedef struct { struct people friends;} Person;typedef struct people { Person person;}; - Ramana Reddy
1
不,想一想。你的递归在哪里结束? - Nikolai Fetissov
2
值得注意的是,使用指向前向声明结构体的指针是使其工作的唯一方法。 - Liviu Chircu

6
关于易读性:
typedef struct Foo_ Foo;
typedef struct Bar_ Bar;

struct Foo_ {
    Bar *bar;
};

struct Bar_ {
    Foo *foo;
};

避免使用typedef struct可能是一个好主意;


2

由于 Person 只需要指向 People 的指针,因此预先声明后者应该是可行的:

typedef struct People People;

然后将第二个声明更改为仅使用结构体标签进行声明,如下所示:
struct People {
    int count;
    int max;
    Person data[];
};

1
struct _People;

typedef struct {
    char* name;
    int age;
    int lefthanded;
    struct _People* friends;
} Person;

struct _People {
    int count;
    int max;
    Person data[1];
};

注意: Person data[]; 是标准的吗?

1
在C99及以上版本中,具有空[]的可变数组成员是标准的。它必须是至少有另一个成员的结构类型的最后一个元素。 - Jonathan Leffler

0
struct People_struct;

typedef struct {
    char* name;
    int age;
    int lefthanded;
    struct People_struct* friends;
} Person;

typedef struct People_struct {
    int count;
    int max;
    Person data[];
} People;

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接