【问题标题】:Preventing recursive C #include防止递归 C#include
【发布时间】:2011-01-03 22:45:19
【问题描述】:

我大致了解#include 对C 预处理器所做的规则,但我并不完全理解。现在,我有两个头文件,Move.h 和 Board.h,它们都定义了它们各自的类型(Move 和 Board)。在这两个头文件中,我都需要引用另一个头文件中定义的类型。

现在我在 Board.h 中有 #include "Move.h",在 Move.h 中有 #include "Board.h"。但是,当我编译时,gcc 会翻转并在 Move.h 和 Board.h 之间翻转给我一个很长(看起来像无限递归)的错误消息。

如何包含这些文件,这样我就不会无限期地递归包含这些文件?

【问题讨论】:

  • 请注意,在理想世界中,您应该避免像这样的循环依赖。当然这并不总是可能的,有时它们可​​能非常有用,但是每当您创建循环依赖项时,您应该花点时间考虑一下并为自己证明它的存在。
  • @Greg D -- 我听取了您的建议并创建了另一个名为 Types.h 的文件,我在其中执行所有 #define-ing 和 typedef-ing。我将它包含在两个文件中,一切都很好!

标签: gcc include c-preprocessor


【解决方案1】:

您需要查看前向声明,您已经创建了包含的无限循环,前向声明是正确的解决方案。

这是一个例子:

移动.h

#ifndef MOVE_H_
#define MOVE_H_

struct board; /* forward declaration */
struct move {
    struct board *m_board; /* note it's a pointer so the compiler doesn't 
                            * need the full definition of struct board yet... 
                            * make sure you set it to something!*/
};
#endif

Board.h

#ifndef BOARD_H_
#define BOARD_H_

#include "Move.h"
struct board {
    struct move m_move; /* one of the two can be a full definition */
};
#endif

main.c

#include "Board.h"
int main() { ... }

注意:每当你创建一个“Board”时,你都需要做这样的事情(有几种方法,这里是一个例子):

struct board *b = malloc(sizeof(struct board));
b->m_move.m_board = b; /* make the move's board point 
                        * to the board it's associated with */

【讨论】:

  • 这看起来是一个很好的解决方案,不幸的是我无法让它工作(我需要研究#ifndef 以及它是如何工作的,才能完全理解这一点)。不过感谢您的回答。
  • 当你说“没用”时,你是什么意思?另外,您的 Move 和 Board 类型是否相互引用,或者两个头文件中使用的类型但不一定在结构定义中? (无耻的插头:另请参阅我的答案以获得一些指示。)
  • 是的,请说明它“不起作用”的方式,这段代码(减去 main 函数)应该可以正常工作。
  • 我的 Move 或 Board 都不是相互组成的,我只需要为一些同时采用 Board 和 Move 的方法定义的类型。当我说“它不起作用”时,它给了我一些关于以前在其他地方定义的方法的奇怪错误。我对您提出的建议进行了一些修改,但我无法让它们中的任何一个起作用。不是拖累,但我对我现在拥有的解决方案感到满意,即将 typedefs 放在单独的头文件中(Greg D 的想法)。
【解决方案2】:

您需要先拥有其中一个。在其中一个中进行前向 decl 并拥有那个例如

    #ifndef move
    struct move;
    #endif

可能是 board.h 文件的一部分。

    #ifndef board
    struct board;
    #endif

可能是 move.h 文件的一部分

那么您可以按任意顺序添加它们。

编辑 正如在 cmets 中指出的那样......我假设对板结构使用 typedef 结构如下

   typedef struct {…} board;

因为我从未见过有人在没有 typedef 的情况下在 C 中使用结构,所以我做出了这个假设……也许自从我上次用 C 编码以来事情已经发生了变化(yikies……就像 15 年前一样)

【讨论】:

  • 您无法使用预处理器测试结构是否存在。如果你暗示他也应该做一个名为 move 的宏,你应该说清楚。
  • 您也无法使用预处理器测试 typedef... 您需要在某处添加类似 #define board 的内容。
  • hmm.. microsoft 的旧 C 编译器曾经允许它,天哪,我想知道我是否能找到对它的引用。好吧,不管 - 许多其他人给出了很好的答案。
【解决方案3】:

像这样:

//Board.h
#ifndef BOARD_H
#define BOARD_H
strunct move_t; //forward declaration
typedef struct move_t Move;
//...
#endif //BOARD_H

//Move.h
#ifndef MOVE_H
#define MOVE_H
#include "Move.h"
typedef struct board_t Board;
//...
#endif //MOVE_H

这样Board.h 可以在不依赖move.h 的情况下编译,您可以从move.h 中包含board.h 以使其内容在那里可用。

【讨论】:

    【解决方案4】:

    包含守卫将是解决此问题的一部分。

    来自维基百科的示例:

    #ifndef GRANDFATHER_H
    #define GRANDFATHER_H
    
    struct foo {
        int member;
    };
    
    #endif
    

    http://en.wikipedia.org/wiki/Include_guard

    其他几个人提到的另一部分是前向引用。 (http://en.wikipedia.org/wiki/Forward_Reference)

    您可以像这样在另一个结构之上部分声明一个结构:

    #ifndef GRANDFATHER_H
    #define GRANDFATHER_H
    
    struct bar;
    struct foo {
        int member;
    };
    
    #endif
    

    【讨论】:

      【解决方案5】:

      来自 K&R The C Programming Language(我的副本中的第 91 页“条件包含”),为您做了一些调整:

      #if !defined (BOARD_H)
      #define BOARD_H
      
      /* contents of board.h go here */
      
      #endif
      

      Move.h 也是如此

      这样,一旦一个标头被包含一次,它就不会再次被包含,因为已经为预处理器定义了“BOARD_H”名称。

      【讨论】:

      • 以双下划线开头的宏是保留的,你不应该使用它们。此外,下划线后跟大写字母也是保留的。
      • @Evan - 更正了上述内容,谢谢您的评论。我以为我的编辑评论会出现在上面,但显然不会!
      【解决方案6】:

      首先,您的.h 文件中似乎缺少include guards,因此您递归地包含它们。这很糟糕。

      其次,您可以进行前向声明。在Move.h

      /* Include guard to make sure your header files are idempotent */
      #ifndef H_MOVE_
      #define H_MOVE_
      
      #include "Board.h"
      
      /* Now you can use struct Board */
      struct Move { struct Board *board; };
      
      #endif
      

      Board.h:

      #ifndef H_BOARD_
      #define H_BOARD_
      
      struct Move; /* Forward declaration.  YOu can use a pointer to
                      struct Move from now on, but the type itself is incomplete,
                      so you can't declare an object of the type itself. */
      struct Board { struct Move *move; }; /* OK: since move is a pointer */
      
      #endif
      

      请注意,如果您需要在两个文件中声明struct Movestruct Board 对象(而不是指向其中之一的指针),此方法将不起作用。这是因为其中一种类型在解析其中一个文件时是不完整类型(上例中为struct Move)。

      因此,如果您需要在两个文件中使用类型,则必须将类型定义分开:具有定义 struct Movestruct Board 的头文件,仅此而已(类似于我上面的示例) ,然后使用另一个同时引用struct Movestruct Board 的头文件。

      当然,你不能让struct Move 包含struct Boardstruct Board 包含struct Move——这将是无限递归,结构体大小也将是无限的!

      【讨论】:

        【解决方案7】:

        循环依赖很麻烦,应该在可行的情况下消除。除了目前给出的前向声明建议(Alok's 是最好的例子),我想提出另一个建议:通过引入第三种类型来打破 Board 和 Move 之间的相互依赖(称为 BoardMoveAssoc 进行说明;我相信你能想出一个不那么糟糕的名字):

        #ifndef H_BOARD_MOVE_ASSOC
        #define H_BOARD_MOVE_ASSOC
        
        #include "Move.h"
        #include "Board.h"
        
        struct BoardMoveAssoc {
            Move m;
            Board b;
        };
        
        ...
        #endif
        

        在此方案下,Board 和 Move 不必相互了解任何信息;两者之间的任何关联都由 BoardMoveAssoc 类型管理。确切的结构将取决于 Move 和 Board 应该如何关联;例如,如果将多个动作映射到单个棋盘,则结构可能看起来更像

         struct BoardMoveAssoc {
             Move m[NUM_MOVES] // or Move *m;
             Board b;
         };
        

        这样,您不必担心前向声明或不完整的类型。您正在将第三种类型引入组合中,但我相信这将更容易理解和维护。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2017-01-07
          • 2018-09-20
          • 2019-10-08
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2015-10-08
          相关资源
          最近更新 更多