Как законно сослаться на неопределенный тип в структуре?

Как часть ответа на другой вопрос, я столкнулся с частью кода как это, которое 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 стандарте, который позволяет эту мягкость для указателей структуры.

8
задан caf 24 May 2010 в 07:25
поделиться

6 ответов

Части стандарта C99, которые вам нужны, - это 6.7.2.3, параграф 7:

Если спецификатор типа в форме Идентификатор структуры или объединения встречается кроме как часть одного из вышеперечисленных формы, и никакое другое объявление идентификатор как тег виден, тогда он объявляет неполную структуру или тип объединения и объявляет идентификатор как тег этого типа.

... и 6.2.5 параграф 22:

Неизвестный тип структуры или объединения. содержание (как описано в 6.7.2.3) неполный тип. Завершено, для всех объявлений этого типа объявление той же структуры или союза тег с его определяющим содержанием позже в такой же объем.

6
ответ дан 5 December 2019 в 10:01
поделиться

Как сказано в предупреждении во втором случае, struct NOTHING_LIKE_xyz - это неполный тип , например void или массивы неизвестного размера. Неполный тип может отображаться только как тип, на который указывает, за исключением массивов неизвестного размера, которые разрешены в качестве последнего члена структуры, что делает саму структуру неполным типом в этом случае. Следующий код не может разыменовать какой-либо указатель на неполный тип (по уважительной причине).

Неполные типы могут предлагать некоторую инкапсуляцию типов данных в C ... Соответствующий параграф в http://www.ibm.com/developerworks/library/pa-ctypes1/ кажется хорошим объяснением.

7
ответ дан 5 December 2019 в 10:01
поделиться

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 , вы не можете связываться с внутренним устройством объекта. Это своего рода инкапсуляция.

2
ответ дан 5 December 2019 в 10:01
поделиться

Вы должны назвать это. В этом случае:

typedef struct {
    struct xyz *z;
} xyz;

не сможет указывать на себя, поскольку z относится к некоторому полностью другому типу, а не к безымянной структуре, которую вы только что определили. Попробуйте следующее:

int main()
{
    xyz me1;
    xyz me2;
    me1.z = &me2;   // this will not compile
}

Вы получите сообщение об ошибке несовместимых типов.

1
ответ дан 5 December 2019 в 10:01
поделиться

Меня тоже это интересовало. Оказывается, 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 . Первая строка будет компилироваться нормально, но вторая выдаст ошибку. Тогда не так много пользы от реализации связанного списка.

0
ответ дан 5 December 2019 в 10:01
поделиться

Что ж ... Все, что я могу сказать, это то, что ваше предыдущее предположение было неверным. Каждый раз, когда вы используете конструкцию 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 .

1
ответ дан 5 December 2019 в 10:01
поделиться
Другие вопросы по тегам:

Похожие вопросы: