Код struct _IO_FILE_plus;
является объявлением имени _IO_FILE_plus
, так что если компилятор увидит, что оно используется в каком-то месте, он знает, что в какой-то момент будет определение, которое фактически описывает его члены.
Модификатор extern
указывает, что названный символ является внешним символом, который существует в каком-то другом модуле компиляции. Код, такой как:
extern struct _IO_FILE_plus _IO_2_1_stdin_;
также является объявлением символа, в данном случае _IO_2_1_stdin_
, сообщая компилятору, что символ является внешним символом, который определен и существует в некоторой другой единице компиляции (файле) и каков тип символа, в данном случае struct _IO_FILE_plus
.
Однако обычно при использовании объявления struct
в каком-либо другом объявлении обычно используется указатель на struct
, поскольку размерstruct
и его расположение не могут быть определены простым объявлением, таким как struct _IO_FILE_plus;
.
Однако в этом случае, поскольку он является внешним, если исходный код не содержит какое-либо утверждение, которое требует, чтобы размер икомпоновка struct
, доступная для компилятора, с использованием объявленного символа таким образом работает.
Так что, если бы у вас был источник, такой как эти операторы:
struct _IO_FILE_plus *myIo = malloc(sizeof(struct _IO_FILE_plus));
struct _IO_FILE_plus myIo = _IO_2_1_stdin_; // no pointers here, struct assignment
они бы сгенерировалиошибка, потому что компилятору нужно определение struct _IO_FILE_plus
, чтобы определить результат sizeof()
или объем памяти, который нужно скопировать для назначения struct
в этих утверждениях.
Однако, если у вас есть такое утверждение, как:
struct _IO_FILE_plus *myIO = &_IO_2_1_stdin_;
, оно будет скомпилировано, потому что компилятору нужно только знать, как найти адрес внешней переменной и поставитьэтот адрес в переменную указателя. Адрес внешней переменной фиксируется загрузчиком, когда приложение загружается и настраивается для запуска.
Если внешняя переменная не существует, вы получите ошибку «неразрешенный внешний символ» при связывании.
Пример библиотеки API
Один из способов может быть полезен, если у вас есть несколько различных объектов или устройств, представленных прокси-объектами, и у вас есть библиотека функций, которую вы хотитеразрешите людям выбирать целевой объект или устройство для функций в нем.
Таким образом, в своей библиотеке вы открываете эти объекты или прокси-объекты как внешние, но сохраняете их внутренние секреты, только предоставляя объявление.
Затем в интерфейсе функции требуется указатель на соответствующий объект или прокси-объект, который будет использоваться с функцией.
Хорошая особенность этого подхода заключается в том, что другие стороны имеют доступ к вашей библиотекевнутренние компоненты могут предоставлять дополнительные прокси-объекты, которые работают с вашей библиотекой but с их собственными прокси-объектами.
Это особенно хорошо работает, когда определение struct
содержит указатели для перехвата функций, которые ваша библиотека будет вызывать для выполнения специфических для устройства операций, о которых знает третья сторона, но у вас их нетк. У подключаемых функций есть определенный интерфейс с набором ожидаемых результатов, и то, как это делается, зависит от поставщика функции подключения.
Исходный файл библиотеки:
struct _IO_FILE_plus {
unsigned char buffer[1024];
int bufptr1;
// … other struct member definitions
int (*hookOne)(struct _IO_FILE_plus *obj); // third party hook function pointer
int (*hookTwo)(struct _IO_FILE_plus *obj); // third party hook function pointer
};
struct _IO_FILE_plus _IO_2_1_stdin_ = { {0}, 0, …. };
struct _IO_FILE_plus _IO_2_1_stdout_ = { {0}, 0, …. };
struct _IO_FILE_plus _IO_2_1_stderr_ = { {0}, 0, …. };
int funcOne (struct _IO_FILE_plus *obj, int aThing)
{
int iResult;
if (obj->hookOne) iResult = obj->hookOne(obj);
// do other funcOne() stuff using the object, obj, provided
return iResult;
}
int funcTwo (struct _IO_FILE_plus *obj, double aThing)
{
int iResult;
if (obj->hookTwo) iResult = obj->hookTwo(obj);
// do other funcTwo() stuff using the object, obj, provided
return iResult;
}
БиблиотекаИсходный файл компилируется нормально, потому что у компилятора есть полное определение struct
. Затем в заголовочном файле, поставляемом с библиотекой, у вас есть эти операторы:
struct _IO_FILE_plus ;
extern struct _IO_FILE_plus _IO_2_1_stdin_ ;
extern struct _IO_FILE_plus _IO_2_1_stdout_ ;
extern struct _IO_FILE_plus _IO_2_1_stderr_ ;
extern int funcOne (struct _IO_FILE_plus *obj, int aThing);
extern int funcTwo (struct _IO_FILE_plus *obj, double aThing);
Все это работает, потому что ни один из этих исходных операторов не требует, чтобы фактическое определение struct
было доступно для компилятора. Компилятору нужно только знать, что где-то определены такие символы.
В исходном файле, использующем их, вы можете иметь выражение вроде:
int k = funcOne(&_IO_2_1_stdin_, 5);
, и опять же это требует только от компилятораЗнайте, что символ существует, и в какой-то момент будет доступен адрес этого символа.
И, как часть дизайна библиотеки, вполне возможно, что макросы препроцессора C используются для того, чтобы скрыть некоторые из этих элементов. Поэтому у вас могут быть такие макросы, как:
#define DO_FUNCONE(io,iVal) funcOne(&(io), (iVal))
#define DO_FUNCONE_STDIN(iVal) funcOne(&_IO_2_1_stdin_,(iVal))
#define IO_STDIN (&_IO_2_1_stdin)
Однако оператор, подобный следующему, не будет компилироваться, потому что компилятор будет предоставлять копию struct
функции, которая принимает значение внешнего элемента, а не указатель на него:
int k = doFuncOne (_IO_2_1_stdin_); // compiler error. definition of struct _IO_FILE_plus not available
где определение функции функции doFuncOne()
выглядит следующим образом:
// compiler error. definition of struct _IO_FILE_plus not available
int doFuncOne (struct _IO_FILE_plus obj) // notice this is struct and not pointer to struct
{
// do some setup then call funcOne().
return funcOne(&obj, 33);
}
Однако изменение интерфейса функции doFuncOne()
позволит ее скомпилировать:
// following would compile as only declaration is needed by the compiler.
int doFuncOne (struct _IO_FILE_plus *obj) // notice this is now pointer to struct
{
// do some setup then call funcOne().
return funcOne(obj, 33);
}
Библиотека может предоставить версию функции funcOne()
, скажем funcOneStruct()
, которая допускает аргумент struct
, а не указатель на struct
, потому что компилятор имеет определение struct
, доступное при компиляцииисходные файлы библиотеки. Однако люди, использующие библиотеку, не смогут использовать эту функцию, потому что у пользователей библиотеки есть только объявление struct
, доступное для них, а не определение struct
.
Такая функция можетможет быть полезно сторонним разработчикам, у которых есть определение struct
, доступное для них, возможно, для клонирования одного из существующих объектов, предоставляемых библиотекой.