Предотвращение рекурсивного C #include - PullRequest
7 голосов
/ 12 января 2010

Я примерно понимаю правила работы #include с препроцессором C , но не совсем понимаю. Прямо сейчас у меня есть два заголовочных файла, Move.h и Board.h, которые оба определяют свой соответствующий тип (Move и Board). В обоих заголовочных файлах мне нужно указать тип, определенный в другом заголовочном файле.

Сейчас у меня есть #include "Move.h" в Board.h и #include "Board.h" в Move.h. Когда я компилирую, gcc вылетает и выдает длинное (что выглядит как бесконечно рекурсивное) сообщение об ошибке между Move.h и Board.h.

Как мне включить эти файлы, чтобы я не использовал рекурсивное включение на неопределенный срок?

Ответы [ 7 ]

14 голосов
/ 12 января 2010

Вам необходимо просмотреть форвардные объявления , вы создали бесконечные циклы включений, форвардные декларации - правильное решение.

Вот пример:

Move.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() { ... }

Примечание: всякий раз, когда вы создаете «доску», вам нужно будет сделать что-то вроде этого (есть несколько способов, вот пример):

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 */
3 голосов
/ 12 января 2010

Включить охранников будет частью решения этой проблемы.

Пример из википедии:

#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
2 голосов
/ 12 января 2010

Во-первых, вам, кажется, не хватает включения охранников в ваши .h файлы, поэтому вы включаете их рекурсивно. Это плохо.

Во-вторых, вы можете сделать предварительную декларацию. В 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

In 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 Move и struct Board (а не указатель на один из них) в обоих файлах, этот метод не будет работать. Это связано с тем, что один из типов является неполным типом во время анализа одного из файлов (struct Move в приведенном выше примере).

Итак, если вам нужно использовать типы в обоих файлах, вам придется выделить определения типов: иметь заголовочные файлы, которые определяют struct Move и struct Board, и ничего больше (что-то вроде моего примера выше) , а затем используйте другой заголовочный файл, который ссылается на struct Move и struct Board.

Конечно, вы не можете struct Move содержать struct Board и struct Board одновременно содержать struct Move - это будет бесконечная рекурсия, а размеры структур также будут бесконечными!

2 голосов
/ 12 января 2010

Вроде так:

//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, и вы можете включить board.h из move.h, чтобы сделать его содержимое доступным там.

0 голосов
/ 12 января 2010

Круговые зависимости - это боль в заднице, и их следует устранять, где это возможно. В дополнение к предложениям о предварительном объявлении, представленным на данный момент (Alok - лучший пример), я хотел бы добавить еще одно предложение: разорвать взаимную зависимость между 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;
 };

Таким образом, вам не нужно беспокоиться о предварительных объявлениях или неполных типах. Вы вводите третий тип в смесь, но я верю, что это будет легче понять и поддерживать.

0 голосов
/ 12 января 2010

Из K & R Язык программирования C (стр. 91 «Условное включение» в моей копии), с некоторыми изменениями для вас:

#if !defined (BOARD_H)
#define BOARD_H

/* contents of board.h go here */

#endif

и то же самое для Move.h

Таким образом, после однократного включения заголовка он не будет включаться снова, поскольку для препроцессора уже определено имя 'BOARD_H'.

0 голосов
/ 12 января 2010

Сначала вам нужно иметь один из них. Сделайте форвард decl в одном из них и используйте его, например,

    #ifndef move
    struct move;
    #endif

может быть частью файла board.h.

и

    #ifndef board
    struct board;
    #endif

может быть частью файла move.h

тогда вы можете добавить их в любом порядке.

редактировать Как было отмечено в комментариях ... Я предполагал использование конструкции typedef следующим образом для структуры правления

   typedef struct {…} board;

так как я никогда не видел, чтобы кто-то использовал структуры в C без typedef, я сделал это предположение ... возможно, все изменилось с тех пор, как я в последний раз кодировал в C (yikies .... это было 15 лет назад)

...