Итак, у меня была небольшая путаница относительно связывания различных вещей. В этом вопросе я сосредоточусь на непрозрачных указателях.
Я проиллюстрирую свое замешательство на примере. Допустим, у меня есть три файла:
main.c
#include <stdio.h>
#include "obj.h" //this directive is replaced with the code in obj.h
int main()
{
myobj = make_obj();
setid(myobj, 6);
int i = getid(myobj);
printf("ID: %i\n",i);
getchar();
return 0;
}
obj.c
#include <stdlib.h>
struct obj{
int id;
};
struct obj *make_obj(void){
return calloc(1, sizeof(struct obj));
};
void setid(struct obj *o, int i){
o->id = i;
};
int getid(struct obj *o){
return o->id;
};
obj.h
struct obj;
struct obj *make_obj(void);
void setid(struct obj *o, int i);
int getid(struct obj *o);
struct obj *myobj;
Из-за директив препроцессора они по сути превратились бы в два файла:
(технически я знаю, что в stdio.h и stdlib.h их код заменит директивы препроцессора, но я не стал заменять их для удобства чтения)
main.c
#include <stdio.h>
//obj.h
struct obj;
struct obj *make_obj(void);
void setid(struct obj *o, int i);
int getid(struct obj *o);
struct obj *myobj;
int main()
{
myobj = make_obj();
setid(myobj, 6);
int i = getid(myobj);
printf("ID: %i\n",i);
getchar();
return 0;
}
obj.c
#include <stdlib.h>
struct obj{
int id;
};
struct obj *make_obj(void){
return calloc(1, sizeof(struct obj));
};
void setid(struct obj *o, int i){
o->id = i;
};
int getid(struct obj *o){
return o->id;
};
Вот где я немного запутался. Если я попытаюсь создать struct obj в main.c, я получаю ошибку неполного типа, хотя main.c имеет объявление struct obj;
.
Даже если я изменю код, чтобы использовать extern
, он не будет компилироваться:
main.c
#include <stdio.h>
extern struct obj;
int main()
{
struct obj myobj;
myobj.id = 5;
int i = myobj.id;
printf("ID: %i\n",i);
getchar();
return 0;
}
obj.c
#include <stdlib.h>
struct obj{
int id;
};
Насколько я могу судить, main.c и obj.c не сообщают структуры (в отличие от функций или переменных для некоторых, которым просто нужно объявление в другом файле).
Итак, main.c не имеет связи с типами struct obj, но по какой-то причине в предыдущем примере он смог создать указатель на один очень хорошо struct obj *myobj;
. Как почему? Я чувствую, что упускаю какую-то важную информацию. Каковы правила относительно того, что можно, а что нельзя переходить из одного файла .c в другой?
ДОБАВЛЕНИЕ
Чтобы устранить возможное дублирование, я должен подчеркнуть, что я не спрашивая, что такое непрозрачный указатель, но как он работает в отношении связывания файлов.
obj.c
должен включатьobj.h
для обеспечения согласованности междуobj.c
иmain.c
- даже если вы используете непрозрачные указатели. - person Jonathan Leffler   schedule 12.04.2020main.c
не имеет сведений оstruct obj
; он знает, что тип существует, но ничего не знает о том, что он содержит. Вы можете создавать и использовать указатели наstruct obj
; вы не можете разыменовать эти указатели, даже чтобы скопировать структуру, не говоря уже о доступе к данным внутри структуры, потому что неизвестно, насколько она велика. Вот почему у вас есть функции вobj.c
- они предоставляют необходимые вам услуги. Распределение объектов, освобождение, доступ и модификация содержимого (за исключением того, что освобождение объекта отсутствует; возможно,free(obj);
в порядке, но лучше предоставить «деструктор»). - person Jonathan Leffler   schedule 12.04.2020struct obj *make_obj(int initializer) { … }
вobj.c
, но поскольку вы не включаетеobj.h
вobj.c
, компилятор не может сказать вам, что ваш код вmain.c
вызовет его без инициализатора, что приведет к квазислучайным (неопределенным) значениям используется для «инициализации» структуры. Если вы включитеobj.h
вobj.c
, компилятор сообщит о несоответствии между объявлением в заголовке и определением в исходном файле, и код не будет компилироваться. Код вmain.c
тоже не компилируется - после исправления заголовка. [… продолжение…] - person Jonathan Leffler   schedule 12.04.2020struct WhatEver;
там, где это необходимо (обычно в области файла, а не внутри функции; существуют сложные правила для определения (или, возможно, переопределения) типов структур внутри функций). Затем вы можете использовать указатели на этот тип без дополнительной информации для компилятора. - person Jonathan Leffler   schedule 12.04.2020struct WhatEver { … };
, где фигурные скобки и содержимое между ними имеют решающее значение) вы не можете получить доступ к тому, что находится в структуре, или создать переменные типаstruct WhatEver
, но вы можете создавать указатели (struct WhatEver ptr = NULL;
). Это важно для «безопасности типов» - избегайтеvoid *
как универсального типа указателя, когда это возможно, и обычно вы можете этого избежать. Не всегда, но обычно. - person Jonathan Leffler   schedule 12.04.2020obj.h
вobj.c
- это средство обеспечения соответствия используемого прототипа определению, вызывая сообщение об ошибке, если они этого не делают. Я до сих пор не совсем понимаю, что все указатели имеют одинаковый размер и выравнивание. Разве размер и выравнивание структуры не будут уникальными для этой конкретной структуры? - person thepufferfish   schedule 12.04.2020void *
в качестве универсального указателя? - person thepufferfish   schedule 12.04.2020{ … }
), то указатель может быть разыменован (и могут быть определены переменные типа структуры, а также указатели на нее, конечно ). Если компилятор не знает подробностей, вы можете определять (и использовать) только указатели на тип. Вы избегаетеvoid *
, потому что теряете всю безопасность типов. Если у вас естьvoid *delicate_and_dangerous(void *vptr)
, вы можете вызыватьdelicate_and_dangerous(stdin);
иdelicate_and_dangerous(argv[1]);
, и компилятор не может жаловаться. [… продолжение…] - person Jonathan Leffler   schedule 12.04.2020struct SpecialCase *delicate_and_dangerous(struct UnusualDevice *udptr)
, то компилятор сообщит вам, когда вы вызовете его с неправильным типом указателя, напримерstdin
(aFILE *
) илиargv[1]
(char *
, если вы вmain()
) и др. - person Jonathan Leffler   schedule 12.04.2020