LINUX.ORG.RU

Структуры, заголовки, typedef'ы и циклические зависимости.

 


0

2

Пытаюсь написать программу из нескольких файлов. Попытаюсь примерно изобразить, чего хочу.

Файл a.h

#ifndef _A_H
#define _A_H
#include "b.h"

typedef struct StructA {
   int a;
   int b;
   int c;
} A; 

void A_f1(A* a, B* b);
void A_f2(A* a, B* b);
void A_f3(A* a, B* b);

#endif

Файл b.h

#ifndef _B_H
#define _B_H
#include "a.h"

typedef struct StructB {
   int a;
   int b;
   int c;
} B; 

void B_f1(A* a, B* b);
void B_f2(A* a, B* b);
void B_f3(A* a, B* b);

#endif

Файл c.h

#ifndef _C_H
#define _C_H
#include "a.h"
#include "b.h"

void C_f1(A* a, B* b);
void C_f2(A* a, B* b);
void C_f3(A* a, B* b);

#endif

Файл main.c

#include "a.h"
#include "b.h"
#include "c.h"

int main() {
   //some function calls...
   return 0;
}

Как видно, в файле a.h нужен #include «b.h», и наоборот (иначе структуры будут не определены). Реализацию функций, прототипы которых объявлены в заголовках, опущу, но будем считать, что они дергают поля всех структур (то есть описать структуру в файлах *.c и оставив в *.h только forward declaration не получится). Собственно, код, похожий на написанный выше не компилируется, поскольку:

  • В main.c включается a.h
  • В a.h включается b.h
  • Компилятор ругается, что в b.h используется необъявленный тип A (a.h, заинклюженый из b.h оказывается пустым, поскольку A_H уже задефайнена)

Погуглив на эту тему, наткнулся на совет: не использовать #инклюды внутри заголовочных файлов. Вместо этого вставлять forward declaration для нужных структур. Попробовал и так. Результат ниже:

Файл a.h

#ifndef _A_H
#define _A_H
//#include "b.h"

typedef struct StructA {
   int a;
   int b;
   int c;
} A; 

struct StructB;
typedef StructB B;

void A_f1(A* a, B* b);
void A_f2(A* a, B* b);
void A_f3(A* a, B* b);

#endif

Файл b.h

#ifndef _B_H
#define _B_H
//#include "a.h"

typedef struct StructB {
   int a;
   int b;
   int c;
} B; 

struct StructA;
typedef StructA A;

void B_f1(A* a, B* b);
void B_f2(A* a, B* b);
void B_f3(A* a, B* b);

#endif

Файл c.h

#ifndef _C_H
#define _C_H
//#include "a.h"
//#include "b.h"

struct StructB;
typedef StructB B;

struct StructA;
typedef StructA A;


void C_f1(A* a, B* b);
void C_f2(A* a, B* b);
void C_f3(A* a, B* b);

#endif

Файл main.c

#include "a.h"
#include "b.h"
#include "c.h"

int main() {
   //some function calls...
   return 0;
}

Подобный код у меня скомпилировался. Как оказалось, только на GCC 4.7. На другой машине с GCC 4.4, компилятор выдывал ошибки про повторные typedef'ы (действительно, typedef'ы в a.h и c.h, включенных в main.c повторяются). Покумекав, придумал свой вариант, который скомпилировался. Каждый из заголовков разбил на два: в первом - описание структуры и прототипы функций, а во втором - только forward definition структуры и typedef. Второй #includ'ится в другие заголовочные файлы, а первый - во все остальные. Результат, опять таки, ниже:

Файл a.h

#ifndef _A_H
#define _A_H
#include "a_short.h"
#include "b_short.h"

struct StructA {
   int a;
   int b;
   int c;
}; 

void A_f1(A* a, B* b);
void A_f2(A* a, B* b);
void A_f3(A* a, B* b);

#endif

Файл a_short.h

#ifndef _A_SHORT_H
#define _A_SHORT_H

struct StructA;
typedef StructA A; 

#endif

Файл b.h

#ifndef _B_H
#define _B_H
#include "a_short.h"
#include "b_short.h"

struct StructB {
   int a;
   int b;
   int c;
}; 

void B_f1(A* a, B* b);
void B_f2(A* a, B* b);
void B_f3(A* a, B* b);

#endif

Файл b_short.h

#ifndef _B_SHORT_H
#define _B_SHORT_H

struct StructB;
typedef StructB B; 

#endif

Файл c.h

#ifndef _C_H
#define _C_H
#include "a_short.h"
#include "b_short.h"

void C_f1(A* a, B* b);
void C_f2(A* a, B* b);
void C_f3(A* a, B* b);

#endif

Файл main.c

#include "a.h"
#include "b.h"
#include "c.h"

int main() {
   //some function calls...
   return 0;
}

Собственно, здесь typedef'ы защищены #ifndef'ами и поэтому появляются в коде только один раз и ошибок не возникает.

Хотелось бы узнать, как обычно решаются подобные проблемы. Варианты, которые удалось придумать мне:

  • То, что получилось у меня с третьего раза. (Количество заголовочных файлов возрастает)
  • Не использовать typedef'ы, использовать forward declaration (второй вариант). (В принципе, typedef'ы используются чтобы укоротить названия типа, можно обойтись и без этого)
  • Забить на старый компилятор (плохая идея, да и не знаю, как в стандарте C обстоят дела с множественными typedef'ами)
  • Слить всё в один заголовочный файл (в этом примере может и прокатит, но в более сложных случаях будет выглядеть страшно)
  • ???

Спасибо всем, кто дочитал до конца, будет здорово, если кто-нибудь что-нибудь посоветует.


Я такие тайпдефы выношу в отдельный файл (сваливаю туда все тайпдефы со всего проекта), это самый разумный вариант.

Второй варинт — можно сделать так :

#ifndef _A_H
#define _A_H

struct StructA;
typedef StructA A;

#include "b.h"

struct StructA {
   int a;
   int b;
   int c;
};

void A_f1(A* a, B* b);
void A_f2(A* a, B* b);
void A_f3(A* a, B* b);

#endif
#ifndef _B_H
#define _B_H

struct StructB;
typedef StructB B;

#include "a.h"

struct StructB {
   int a;
   int b;
   int c;
};

void B_f1(A* a, B* b);
void B_f2(A* a, B* b);
void B_f3(A* a, B* b);

#endif
geekless ★★
()

Универсального ответа скорее всего нет.

Но по моему стоит сделать один файл с предварительными typedef.

либо каждый typedef обрамить ifdef

Ushenin
()
Ответ на: комментарий от geekless

а это:

struct StructA;
typedef StructA A;
можно в одну строчку записать:
typedef struct StructA A;

anonymous
()

Это же сишечка. Какие тайпдефы? Только расовые дефайны.

Вообще, конечно, циклические зависимости в определениях типов свидетельствуют, что ты либо перемудрил, либо недомудрил с проектированием. man KISS.

schizoid ★★★
()
Ответ на: комментарий от schizoid

Это же сишечка. Какие тайпдефы? Только расовые дефайны.

У вас кресты головного мозга.

Именно тайпдефы. Именно в сях.

geekless ★★
()
Ответ на: комментарий от schizoid

Вообще, конечно, циклические зависимости в определениях типов свидетельствуют, что ты либо перемудрил, либо недомудрил с проектированием. man KISS.

/глядя на код в редакторе/ Панель содержит список апплетов. Апплет содержит указатель на свою панель. А теперь расскажи мне про KISS.

geekless ★★
()

В ядре Linux например typedef'ы для структур не используется, используется forward declaration.

Deleted
()
16 мая 2014 г.
Ответ на: комментарий от Deleted

как то не совсем понял я, каким образом можно сделать forward declaration на

typedef struct _FLOAT32 {
	int val;
	int scaleFactor;
	int offset;
} FLOAT32, *pFLOAT32;

splinter ★★★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.