Я использовал следующий код, чтобы создать различную структуру, но только дать людям за пределами файла C подсказку на него. (Да, я знаю, что они могли потенциально бездельничать с ним, таким образом, это не полностью похоже на частное ключевое слово в Java, но это хорошо со мной).
Так или иначе я использовал следующий код, и я посмотрел на него сегодня, и я действительно удивлен, что он на самом деле работает, кто-либо может объяснить, почему это?
В моем файле C я создаю свою структуру, но не даю ему тег в пространстве имен определения типа:
struct LABall {
int x;
int y;
int radius;
Vector velocity;
};
И в файле H, я поместил это:
typedef struct LABall* LABall;
Я, очевидно, использую #include "LABall.h" в c файле, но я НЕ использую #include "LABall.c" в заголовочном файле, поскольку это победило бы целую цель отдельного заголовочного файла. Так, почему могут я для создания указателя на LABall* структура в файле H, когда я на самом деле не включал его? Это имеет некоторое отношение к пространству имен структуры, работающему через файлы, даже когда один файл никоим образом не связан с другим?
Спасибо.
Поскольку вы спрашиваете точную причину того, "почему" язык работает именно так, я предполагаю, что вам нужны точные ссылки. Если вы считаете это педанством, просто пропустите примечания...
Это работает из-за двух вещей:
Все типы указателей на структуры имеют одинаковое представление (обратите внимание, что это не верно для всех типов указателей, насколько это касается стандартного C).[1] Следовательно, компилятор имеет достаточно информации, чтобы генерировать правильный код для всех случаев использования вашего типа указателя на структуру.
Пространство имен тегов (struct, enum, union) действительно совместимо во всех единицах трансляции.[2] Таким образом, две структуры (даже если одна из них не полностью определена, т.е. в ней отсутствуют объявления членов) являются одним и тем же.
(BTW, #import является нестандартным.)
[1] Согласно n1256 §6.2.5.27:
Все указатели на типы структур должны иметь те же требования к представлению и выравниванию, что и друг друга. Указатели на другие типы не обязательно должны иметь те же требования к представлению или выравниванию.
[2] Согласно n1256 §6.2.7.1:
два структурных, объединенных или перечислимых типа, объявленные в отдельных единицах трансляции, совместимы, если их теги и члены удовлетворяют следующим требованиям: Если один из них объявлен с тегом, то другой должен быть объявлен с тем же тегом. Если оба типа являются полными типами, то применяются следующие дополнительные требования: [не относится к нам].
В
typedef struct A* B;
, поскольку интерфейсы всех указателей одинаковы, известно, что B означает, что указатель на структуру A уже содержит достаточно информации. Фактическая реализация A не имеет значения (этот метод называется «непрозрачный указатель».)
(Кстати, лучше переименуйте один из LABall
. Это сбивает с толку, что то же имя используется для несовместимых типов .)
Стандартный шаблон для подобных вещей должен иметь файл foo.h
, определяющий API, например
typedef struct _Foo Foo;
Foo *foo_new();
void foo_do_something(Foo *foo);
, и файл foo.c
, обеспечивающий реализацию этого API, например
struct _Foo {
int bar;
};
Foo *foo_new() {
Foo *foo = malloc(sizeof(Foo));
foo->bar = 0;
return foo;
}
void foo_do_something(Foo *foo) {
foo->bar++;
}
Это скрывает всю структуру памяти и размер структуры в реализации в foo.c
, а интерфейс, предоставляемый через foo.h
, полностью независим от этих внутренних компонентов: A caller.c
, который выполняет только #include "foo.h"
, должен будет хранить только указатель на что-то, причем указатели всегда имеют одинаковый размер:
#include "foo.h"
void bleh() {
Foo *f = foo_new();
foo_do_something(f);
}
Примечание: Я оставил освобождение памяти в качестве упражнения для читателя. : -)
Конечно, это означает, что следующий файл broken.c
будет НЕ работать:
#include "foo.h"
void broken() {
Foo f;
foo_do_something(&f);
}
как размер памяти, необходимый для фактического создания переменной типа Foo
не известен в этом файле.