Как часть ответа на другой вопрос, я столкнулся с частью кода как это, которое gcc компилирует без жалобы.
typedef struct {
struct xyz *z;
} xyz;
int main (void) {
return 0;
}
Это - средства, я всегда раньше создавал типы, которые указывают себе (например, связанные списки), но я всегда думал, что необходимо было назвать структуру, таким образом, Вы могли использовать самоссылку. Другими словами, Вы не могли использовать xyz *z
в структуре, потому что определение типа еще не завершено в той точке.
Но этот конкретный образец не называет структуру, и это все еще компилирует. Я думал первоначально была некоторая черная магия, продолжающаяся в компиляторе, который автоматически перевел вышеупомянутый код, потому что структура и имена определения типа были тем же.
Но эта маленькая красота работает также:
typedef struct {
struct NOTHING_LIKE_xyz *z;
} xyz;
Что я пропускаю здесь? Это кажется четким нарушением, так как существует нет struct NOTHING_LIKE_xyz
введите определенный где угодно.
Когда я изменяю его от указателя до фактического типа, я получаю ожидаемую погрешность:
typedef struct {
struct NOTHING_LIKE_xyz z;
} xyz;
qqq.c:2: error: field `z' has incomplete type
Кроме того, когда я удаляю struct
, Я получаю ошибку (parse error before "NOTHING ...
).
Это позволяется в ISO C?
Обновление: A struct NOSUCHTYPE *variable;
также компиляции, таким образом, это не просто внутренние структуры, где это, кажется, допустимо. Я ничего не могу найти в c99 стандарте, который позволяет эту мягкость для указателей структуры.
Части стандарта C99, которые вам нужны, - это 6.7.2.3, параграф 7:
Если спецификатор типа в форме
Идентификатор структуры или объединения
встречается кроме как часть одного из вышеперечисленных формы, и никакое другое объявление идентификатор как тег виден, тогда он объявляет неполную структуру или тип объединения и объявляет идентификатор как тег этого типа.
... и 6.2.5 параграф 22:
Неизвестный тип структуры или объединения. содержание (как описано в 6.7.2.3) неполный тип. Завершено, для всех объявлений этого типа объявление той же структуры или союза тег с его определяющим содержанием позже в такой же объем.
Как сказано в предупреждении во втором случае, struct NOTHING_LIKE_xyz
- это неполный тип , например void
или массивы неизвестного размера. Неполный тип может отображаться только как тип, на который указывает, за исключением массивов неизвестного размера, которые разрешены в качестве последнего члена структуры, что делает саму структуру неполным типом в этом случае. Следующий код не может разыменовать какой-либо указатель на неполный тип (по уважительной причине).
Неполные типы могут предлагать некоторую инкапсуляцию типов данных в C ... Соответствующий параграф в http://www.ibm.com/developerworks/library/pa-ctypes1/ кажется хорошим объяснением.
1-й и 2-й случаи четко определены, поскольку размер и выравнивание указателя известны. Компилятору C нужна только информация о размере и выравнивании для определения структуры.
Третий случай недопустим, потому что размер этой фактической структуры неизвестен.
Но учтите, что для того, чтобы 1-й случай был логичным, вам нужно дать имя структуре:
// vvv
typedef struct xyz {
struct xyz *z;
} xyz;
в противном случае внешняя структура и * z
будут считаться двумя разными структурами.
Второй вариант имеет популярный вариант использования, известный как «непрозрачный указатель» (pimpl) . Например, вы можете определить структуру оболочки как
typedef struct {
struct X_impl* impl;
} X;
// usually just: typedef struct X_impl* X;
int baz(X x);
в заголовке, а затем в одном из .c
,
#include "header.h"
struct X_impl {
int foo;
int bar[123];
...
};
int baz(X x) {
return x.impl->foo;
}
преимущество вне этого .c
, вы не можете связываться с внутренним устройством объекта. Это своего рода инкапсуляция.
Вы должны назвать это. В этом случае:
typedef struct {
struct xyz *z;
} xyz;
не сможет указывать на себя, поскольку z
относится к некоторому полностью другому типу, а не к безымянной структуре, которую вы только что определили. Попробуйте следующее:
int main()
{
xyz me1;
xyz me2;
me1.z = &me2; // this will not compile
}
Вы получите сообщение об ошибке несовместимых типов.
Меня тоже это интересовало. Оказывается, struct NOTHING_LIKE_xyz * z
является прямым объявлением struct NOTHING_LIKE_xyz
. В качестве запутанного примера:
typedef struct {
struct foo * bar;
int j;
} foo;
struct foo {
int i;
};
void foobar(foo * f)
{
f->bar->i;
f->bar->j;
}
Здесь f-> bar
относится к типу struct foo
, а не typedef struct {...} foo
. Первая строка будет компилироваться нормально, но вторая выдаст ошибку. Тогда не так много пользы от реализации связанного списка.
Что ж ... Все, что я могу сказать, это то, что ваше предыдущее предположение было неверным. Каждый раз, когда вы используете конструкцию struct X
(сама по себе или как часть более крупного объявления), она интерпретируется как объявление типа структуры с тегом struct X
. Это может быть повторное объявление ранее объявленного типа структуры. Или это может быть самое первое объявление типа структуры новый . Новый тег объявляется в той области, в которой он появляется. В вашем конкретном примере это область видимости файла (поскольку язык C не имеет «области класса», как это было бы в C ++).
Более интересным примером такого поведения является ситуация, когда объявление появляется в прототипе функции:
void foo(struct X *p); // assuming `struct X` has not been declared before
В этом случае новое объявление struct X
имеет область видимости прототипа функции , которая заканчивается в конце прототипа. Если вы объявите область видимости файла struct X
позже
struct X;
и попытаетесь передать указатель типа struct X
на указанную выше функцию, компилятор выдаст вам диагностическую информацию о не- соответствующий тип указателя
struct X *p = 0;
foo(p); // different pointer types for argument and parameter
Это также сразу означает, что в следующих объявлениях
void foo(struct X *p);
void bar(struct X *p);
void baz(struct X *p);
каждое объявление struct X
является объявлением другого типа, каждое локально для своей собственной области действия прототипа функции .
Но если вы предварительно объявите struct X
, как в
struct X;
void foo(struct X *p);
void bar(struct X *p);
void baz(struct X *p);
, все ссылки struct X
во всех прототипах функции будут ссылаться на ту же , ранее объявленную тип struct X
.