【问题标题】:Is it wrong to include circular dependencies in two structs in two different header files?在两个不同的头文件中的两个结构中包含循环依赖是错误的吗?
【发布时间】:2017-07-20 01:45:41
【问题描述】:

我有一个非常大的程序没有编译,我怀疑它与结构之间的循环依赖有关。当我像下面这样编码时,它不会编译

foo.h

#ifndef FOO
#define FOO

#include "bar.h"


typedef struct _foo Foo;

struct _foo{
  Bar *bar;
}

#endif

bar.h

#ifndef BAR
#define BAR

#include "foo.h"

typedef struct _bar Bar;

struct _bar{
  Foo *foo;
}

#endif

但如果我完全搞砸了我的设计并创建了一个 common.h 文件,并将所有结构声明放入其中,它似乎可以工作。

common.h

#ifndef COMMON
#define COMMON

typedef struct _foo Foo;
typedef struct _bar Bar;

#endif

foo.h

#ifndef FOO
#define FOO

#include "common.h"
#include "bar.h"

struct _foo{
  Bar *bar;
}

#endif

bar.h

#ifndef BAR
#define BAR

#include "common.h"
#include "foo.h"

struct _bar{
  Foo *foo;
}

#endif

这似乎是非常糟糕的设计。此外,我认为标头保护旨在防止由循环包含引起的问题。我应该使用 common.h 方法,还是我做错了什么导致我的第一个解决方案失败?

【问题讨论】:

  • 循环包含总是错误的。
  • “标题保护旨在防止由循环包含引起的问题”。包含守卫旨在打破循环。这使得循环包含毫无意义(因为警卫无论如何都会阻止它)。但是,包含守卫并不能帮助您解决最初尝试通过使用循环包含来解决的依赖问题。
  • @AnT 那么为简单起见,将所有结构声明 dump 放在一个头文件中是否有意义?我担心的是我最终会得到一个 common.h 文件,其中声明的结构彼此无关
  • 你的例子已经足够好了。只需假设foo.h 是较低级别的标头,bar.h 是较高级别的标头。将foo.h 包含到bar.h 中(就像您现在所做的一样),但不要将bar.h 包含到foo.h 中。相反,只需将typedef struct _bar Bar; 添加到foo.h。完毕。 (并且不要忘记结构类型声明末尾的分号)。
  • @AnT 如果我有两个大组件 A 和 B,每个组件都有 200 行长,我真的不想将它们组合成一个 400 行的文件。

标签: c struct c-preprocessor header-files


【解决方案1】:

不同文件中的相互递归结构没有问题。

这里的主要问题是循环包含不起作用。然而,解决这个问题很容易,因为两个标题都不能包含另一个。

如果你跳过typedef,你可以这样做:

// In a.h
struct a {
    struct b *ptr;
};

// In b.h
struct b {
    struct a *ptr;
};

或者

如果你想使用 typedef,你只需要在结构定义中跳过使用它。

// In a.h
struct a {
    // Have to use 'struct b' instead of 'b' because we can't guarantee
    // that the typedef for b is visible yet.
    struct b *ptr;
};
typedef struct a a;

// In b.h
struct b {
    // Have to use 'struct a' instead of 'a' because we can't guarantee
    // that the typedef for a is visible yet.
    struct a *ptr;
};
typedef struct b b;

【讨论】:

  • 所以如果我使用struct a *ptr; 而不是typedef struct a A;...;A *ptr 那么我可以有循环依赖吗?这与 AnT 上面的评论形成鲜明对比
  • @puk:AnT 说的是循环头文件包含。
  • @puk:另外请注意,您没有特别的理由将其命名为 struct _bar 而不仅仅是 struct bar。既然struct bar 很好,为什么不这样做呢?
  • 我在某处看到了带有下划线的结构名称约定并开始使用它。
  • @puk:同样,您不必将它们合并到一个文件中。
【解决方案2】:

使用以下 C 文件:

#include <stdio.h>
#include "foo.h"
#include "bar.h"

int main()
{
    Foo f = { NULL };
    Bar b = { NULL };
    printf("hello\n");
    return 0;
}

预处理器输出以下内容(不包括stdio.h的内容):

# 2 "x1.c" 2
# 1 "foo.h" 1



# 1 "bar.h" 1



# 1 "foo.h" 1
# 5 "bar.h" 2

typedef struct _bar Bar;

struct _bar{
  Foo *foo;
}
# 5 "foo.h" 2
typedef struct _foo Foo;

struct _foo{
  Bar *bar;
}
# 3 "x1.c" 2


int main()
{
    Foo f = { ((void *)0) };
    Bar b = { ((void *)0) };
    printf("hello\n");
    return 0;
}

请注意,Foo 的 typedef 被引用之后出现。这就是导致错误的原因。因为FooBar 相互依赖,所以最好将两者定义在同一个标​​头中,并且每个都先定义typedef。

【讨论】:

  • 检测到 EPIC 答案。谢谢,这非常非常很有帮助
【解决方案3】:

每种类型都必须在使用前声明(但不一定定义)。这不会发生在您的第一种方法中。假设您要在主程序中包含bar.h。因为foo.h 包含在其中,并且由于您的标头保护结构,编译器会生成如下内容:

typedef struct _foo Foo;

struct _foo{
  Bar *bar;
}

typedef struct _bar Bar;

struct _bar{
  Foo *foo;
}

int main( int argc, char* argv[] ){
  //Program stuff
}

然后它会尝试从上到下编译。问题是它达到了定义

struct _foo{
  Bar *bar;
}

不知道该怎么做,因为编译器还没有看到Bar

解决方案是使用前向声明 向编译器声明将有一个名为struct _bar 的类型,并且您保证稍后会定义此类型。例如,下面的编译就好了:

struct _bar; //Forward declaration of 'struct _bar'

struct _foo{ //Definition of 'struct _foo'
  struct _bar myBar;
}

struct _bar{ //Definition of 'struct _bar'
  struct _foo myFoo;
}

特别是在您的情况下,将所有前向声明放在专用头文件中,或者仅在每个头文件中前向声明每种类型都是一种很好的形式。例如:

bar.h

#ifndef BAR
#define BAR

struct _foo; //Defined in foo.h, circular dependency

struct _bar{
  struct _foo *foo;
}

#endif

foo.h

#ifndef FOO
#define FOO

struct _bar; //Defined in bar.h, circular dependency

struct _foo{
  struct _bar *bar;
}

#endif

正如在 cmets 中所指出的,标头保护不会为您“解决”这个问题——它们只是防止每个头文件被多次包含(这通常会导致重新定义错误)。正确的解决方案是确保编译器在您使用它之前知道您将要使用的每种类型。

【讨论】:

  • 除了函数参数之外,您不需要单独的 struct 标签前向声明。
  • 两个问题。 1)随机前向声明浮动是否不错?例如,如果我在一个头文件中定义,然后在 100 个其他文件中转发声明,这对我来说似乎很糟糕。 2) 为了简单起见,我可以用typedef 转发声明吗(我注意到我对使用typedef 感到有些不满)
  • @puk 对于大型项目,我倾向于创建一个包含所有定义的头文件。对于一个小型项目,或者一个结构紧密耦合而无法在其他任何地方使用的项目,拥有两个相互前向声明不是问题。
  • @David 您会在单个文件中定义所有结构体,还是声明单个文件中的所有结构体?
  • @puk 定义。您必须在这里或那里使用前向声明来解决任何循环依赖关系,但通常不需要单独的声明。
【解决方案4】:

我看到一个明显的问题——在foo.h 的标头保护中,您使用结构的名称作为保护。这应该是宏的全部大写。我猜这是某种 IDE 自动替换,它意外地溜进了你的代码?

您遇到的问题可能不止于此,但仍然如此。 =]

【讨论】:

  • 抱歉,我很快就把它搞定了。我现在就编辑
猜你喜欢
  • 2012-06-29
  • 2014-06-30
  • 1970-01-01
  • 2013-05-23
  • 1970-01-01
  • 2019-11-04
  • 1970-01-01
  • 2021-08-15
  • 1970-01-01
相关资源
最近更新 更多