Правильно ли я предполагаю, что нельзя переадресовать – объявить непрозрачный тип указателя библиотеки?

Есть много вопросов о форвардных декларациях и непрозрачных типах, но большинство из них, похоже, с точки зрения автора библиотеки или людей, пытающихся использовать неполные типы без указателей или некоторых таких.

Я использую библиотеку, интерфейс которой принимает / возвращает указатели FOO * . Я хотел бы подтвердить, что я не могу (или не должен) каким-то образом переслать – объявить FOO или FOO * в моем файле заголовка (который определяет структуру с членом FOO * ).

Я знаю, что я мог бы просто #include как в моем заголовке, так и в моем .c файле, но поскольку это действительно просто учебный проект, я хотел получить разъяснения. (С одной стороны, кажется, что может быть возможна форвардная декларация, поскольку мой член структуры является только указателем, и, следовательно, его размер известен, не зная, что такое FOO но, с другой стороны, я не знаю, valid / smart, чтобы typedef что-то FOO когда библиотека уже делает это.)

Заранее спасибо!

Предполагая, что вам не нужно разыгрывать указатель, тогда вы можете использовать непрозрачный указатель типа, если вы знаете имя тега структуры для него:

 typedef struct FOO FOO; 

Теперь вы можете создавать переменные FOO * и использовать их. И вы, вероятно, можете найти тег структуры из файла заголовка, но вы должны знать, что владельцы библиотеки могут его изменить в любое время.

Обычно лучше всего включать официальный заголовок, но если большая часть вашего кода не обращается к фактической библиотеке, просто передавая дескриптор на что-то, возвращаемое библиотекой, вы можете избежать «стоимости» включения фактического заголовка. Вы должны оценить, что это за стоимость, прежде чем принимать решение о том, что, вероятно, преждевременно оптимизируется. Можно утверждать, что если вы должны задать вопрос, вы не знаете достаточно, чтобы быть уверенным в том, чтобы делать это правильно, и вам угрожает сожжение.

Обратите внимание: вы не можете создавать фактические переменные типа; для этого, чтобы компилятор должен был знать, насколько велика структура на самом деле, это означает, что вам нужны детали из заголовка.

Строго говоря, если вы не знаете имя тега, это не сработает. Точно так же, если в структуре нет тега, вы тоже не можете этого сделать. И если это не тип структуры, вы не можете этого сделать.

Обратите внимание: если вы знаете тег структуры, вы также можете написать:

 struct FOO *fp; 

Если вам нужно изобретать, все работает для прохода вокруг указателей, пока вы не достигнете точки, где вам нужно получить доступ к фактическим библиотечным функциям. Тогда вам нужен фактический заголовок библиотеки (чтобы убедиться, что информация правильная), и если ваш тег структуры ошибочен, все ад разрывается. Итак, если вы собираетесь играть в эту игру, убедитесь, что вы правильно настроили тег структуры.

Обратите внимание, что C11 позволяет повторять typedef до тех пор, пока он будет одинаковым каждый раз, тогда как более ранние версии C этого не позволяют. Это может быть значительной помощью.

Рабочий пример

Это близко к минимальному примеру, показывающему, как это можно сделать. Он предполагает C11, где повторный typedef является законным. Он не будет работать для C99 или C89 / C90, потому что typedef для FOO повторяется при projfunc.c . (Существуют различные способы адаптировать его, чтобы он работал на C99 или ранее, но они более беспорядочны, используя #ifdef или аналогичные вокруг project.h typedef – поскольку презумпция заключается в том, что вы не можете изменить library.h , если это возможно, это часть вашего проекта.)

Заголовок project.h используется главным образом общим кодом, который принадлежит проекту, который использует библиотеку, определяющую FOO которая представлена ​​в этом примере projmain.c . Его можно использовать самостоятельно или с помощью projfunc.c , который проиллюстрирован projfunc.c который является кодом проекта, который фактически взаимодействует с библиотекой и вызывает вызовы в библиотеке. Файл library.c использует только library.h .

Вы можете играть с альтернативными объявлениями FOO в project.h чтобы увидеть, что происходит не так. Например, typedef struct BAR FOO; не удастся; так будет typedef struct FOO *FOO; ,

project.h

 #ifndef PROJECT_H_INCLUDED #define PROJECT_H_INCLUDED typedef struct FOO FOO; typedef struct Project { FOO *foop; char *name; int max; double ratio; } Project; extern int proj_function(Project *pj); #endif /* PROJECT_H_INCLUDED */ 

library.h

 #ifndef LIBRARY_H_INCLUDED #define LIBRARY_H_INCLUDED typedef struct FOO { int x; int y; } FOO; extern FOO *foo_open(const char *file); extern int foo_close(FOO *foop); extern int foo_read(FOO *foop, int *x, int *y); extern int foo_write(FOO *foop, int x, int y); #endif /* LIBRARY_H_INCLUDED */ 

projmain.c

 #include "project.h" int main(void) { Project pj = { 0, 0, 0, 0.0 }; if (proj_function(&pj) != 0) return 1; return 0; } 

projfunc.c

 #include "project.h" #include "library.h" #include  int proj_function(Project *pj) { int x, y; pj->foop = foo_open("classic-mode"); if (foo_write(pj->foop, 1, 2) < 0) { foo_close(pj->foop); return -1; } if (foo_read(pj->foop, &x, &y) < 0) { foo_close(pj->foop); return -1; } printf("x = %d, y = %d\n", x, y); return 0; } 

library.c

 #include "library.h" #include  static FOO foo = { 0, 0 }; FOO *foo_open(const char *file) { assert(file != 0); return &foo; } int foo_close(FOO *foop) { assert(foop == &foo); foo.x = foo.y = 0; return 0; } int foo_read(FOO *foop, int *x, int *y) { assert(foop == &foo); *x = foop->x + 1; *y = foo.y + 1; return 0; } int foo_write(FOO *foop, int x, int y) { assert(foop == &foo); foo.x = x + 1; foop->y = y + 2; return 0; } 

Библиотека должна определять FOO для вас, как непрозрачно, так и прозрачно, поскольку ее собственный источник относится к FOO.

#include должен предоставить вам прототипы функций, предоставляемых библиотекой, а также типы, необходимые для взаимодействия с ними.

Если вы создадите свой собственный тип FOO, вы почти наверняка получите ошибку компиляции, указывающую на множественное определение «FOO», когда вы включаете прототипы функций из библиотеки.