Typedef и структура в файлах C и H

Я использую следующий код для создания различных структур, но только даю людям за пределами файла C указатель на него. (Да, я знаю, что они потенциально могут возиться с ним, поэтому это не совсем похоже на ключевое слово private в Java, но со мной все в порядке).

в любом случае, я использовал следующий код, и я посмотрел на него сегодня, и я очень удивлен, что он действительно работает, Может кто-нибудь объяснить, почему это?

в моем файле C я создаю свой struct, но не давайте ему тег в пространстве имен typedef:

struct LABall {
    int x;
    int y;
    int radius;
    Vector velocity;
};

и в файле H я помещаю это:

typedef struct LABall* LABall;

Я, очевидно, использую #include " LABall.h "в файле c, но я не использую #include" LABall.c " в заголовочном файле, так как это уничтожит всю цель отдельного заголовочного файла. Итак, почему я могу создать указатель на LABall * struct в файле H, когда я фактически не включил его? Имеет ли это какое-то отношение к пространству имен struct работа с файлами accross, даже если один файл никоим образом не связан с другим?

спасибо.

3 ответов


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

Это работает из-за двух вещей:

  • все указатели на типы структур имеют одинаковое представление (обратите внимание, что это не true для всех типов указателей, насколько это касается стандарта C).[1] следовательно, компилятор имеет достаточно информации для генерации правильного кода для все виды использования типа указатель-структура.

  • пространство имен тегов (struct, enum, union) действительно совместимо со всеми единицами перевода.[2] Таким образом, две структуры (даже если одна не полностью определена, т. е. в ней отсутствуют объявления членов) являются одним и тем же.

(кстати, #импорт нестандартен.)

[1] согласно n1256 §6.2.5.27:

все указатели на типы структур должны быть одинаковыми требования к представлению и выравниванию как друг друга. Указатели на другие типы не должны иметь одинаковые требования к представлению или выравниванию.

[2] согласно n1256 §6.2.7.1:

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


стандартный шаблон для таких вещей, чтобы иметь foo.h файл, определяющий API как

typedef struct _Foo Foo;

Foo *foo_new();
void foo_do_something(Foo *foo);

и a 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 неизвестно в этом файле.


на

typedef struct A* B;

поскольку все интерфейсы указателей одинаковы, зная, что B означает указатель на структуру A, уже содержит достаточно информации. Фактическая реализация A не имеет значения (этот метод называется "непрозрачный указатель".)

(кстати, лучше переименовать LABall ' s. Это сбивает с толку, что одно и то же имя используется для несовместимых типов.)