Все идентификаторы структуры автоматически объявляются автоматически

В то время как ответ предупреждение: назначение из несовместимого типа указателя для массива linklist, я заметил, что любой необъявленный идентификатор, связанный с ключевым словом struct, считается просроченным объявленным идентификатором.

Например, программа ниже хорошо компилируется:

/* Compile with "gcc -std=c99 -W -Wall -O2 -pedantic %" */
#include <stdio.h>

struct foo 
{
    struct bar *next;  /* Linked list */
};


int main(void) {
    struct bar *a = 0;
    struct baz *b = 0;
    struct foo c = {0};

    printf("bar -> %p\n", (void *)a);
    printf("baz -> %p\n", (void *)b);
    printf("foo -> %p, %zu\n", (void *)&c, sizeof c); /* Remove %zu if compiling with -ansi flag */
    return 0;
}

Мой вопрос:. Какое правило управляет компилятором C для обработки необъявленных struct identifier в качестве объявленных просроченных типов struct?

Ответ 1

В стандарте говорится (6.2.5.28)

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

Это означает, что компилятор знает, как представлять указатели в любую структуру, даже те, которые (пока) undefined.
Ваша программа имеет дело только с указателями на такие структуры, так что это нормально.

Ответ 2

Это описано в 6.2.5. Типы и 6.7.2.3 Тэги.

struct identifier - тип объекта.

6.2.5 Типы

  • Значение значения, хранящегося в объекте или возвращаемого функцией, определяется тип выражения, используемого для доступа к нему. (Идентификатором, объявленным как объект, является простейшее такое выражение; тип указан в объявлении идентификатора.) Типы разделены на типы объектов (типы, описывающие объекты) и типы функций (типы которые описывают функции). В разных точках внутри единицы перевода может существовать тип объекта неполный (отсутствие достаточной информации для определения размера объектов этого типа) или полный (имеющий достаточную информацию). 37)

37) Тип может быть неполным или полным в течение всей единицы перевода или он может изменять состояния при разные точки внутри единицы перевода.

  1. Тип неизвестного размера массива является неполным. Он завершен, для идентификатора этот тип, указав размер в более поздней декларации (с внутренней или внешней связью). Структура или тип объединения неизвестного содержимого (как описано в 6.7.2.3) является неполным типом. Он заполняется для всех объявлений такого типа путем объявления той же структуры или union tag с его определяющим контентом позже в той же области.

6.7.2.3 Теги

  1. Все объявления структуры, объединения или перечисляемых типов, которые имеют одинаковую область и используйте тот же тег, объявляющий тот же тип. Независимо от того, есть ли тег или что другие декларации типа находятся в одной и той же единице перевода, тип неполный 129) пока сразу после закрывающей скобки списка, определяющего контент, и не завершится в дальнейшем.

129) Неполный тип может использоваться только тогда, когда размер объекта такого типа не требуется. Это не необходимо, например, когда объявлено имя typedef как спецификатор для структуры или объединения или когда объявляется указатель на или функцию, возвращающую структуру или объединение. (См. Неполные типы в 6.2.5.) Спецификация должна быть полной до того, как такая функция будет вызвана или определена.

Ответ 3

В дополнение к ответу, предоставленному 2501, и вашему комментарию к нему: "В моем случае нет даже прямого объявления", следующее.

Любое использование struct tag считается как (вперед) объявление типа структуры, если оно не было объявлено ранее. Хотя более формальным способом было бы сказать, что это просто считается типом, поскольку в стандарте C не упоминаются "форвардные объявления типов структуры", просто завершите и неполные структуры (6.2.5p22).

6.7.2 Спецификаторы типов говорят нам, что конструктор-или-union-спецификатор является спецификатором типа, а 6.7.2.1. Спецификаторы структуры и объединения, параграф 1, говорят о том, что идентификатор struct, в свою очередь, является спецификатором struct-or-union.

Предположим, у вас есть объявление связанного списка, что-то вроде

struct node {
    struct node *next;
    int element;
};

то для этой структуры важно "неявное прямое объявление" этого неполного типа. В конце концов, тип struct node завершен только в завершающей точке с запятой. Но вам нужно обратиться к нему, чтобы объявить указатель next.

Кроме того, объявление struct node (неполного типа) может выйти из области видимости, как и любое другое объявление. Это происходит, например, если у вас есть прототип

int function(struct unknown *parameter);

где struct unknown выходит за пределы области действия сразу же в конце объявления. Другие объявленные struct unknown тогда не совпадают с этим. Это подразумевается в тексте 6.2.5p22:

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

Вот почему gcc предупреждает об этом:

foo.c:1:21: warning: 'struct unknown' declared inside parameter list
foo.c:1:21: warning: its scope is only this definition or declaration, which is probably not what you want

Вы можете исправить это, добавив перед ним дополнительное объявление вперед, которое заставляет область запускаться раньше (и, следовательно, заканчивается позже):

struct unknown;
int function(struct unknown *parameter);

Ответ 4

Я думаю, что самый элегантный вариант использования, где используются неполные типы структур, выглядит примерно так:

struct foo 
{
    struct bar *left;
    struct bar *right;
};
struct bar
{
    int something;
    struct foo *next;
};

т.е. двойная рекурсия, где точки b и b указывают на a. Такие случаи могут быть причиной, почему эта функция была включена в оригинальную спецификацию языка C.

Оригинальный вопрос: автоматически ли объявляются все идентификаторы структуры. Я думаю, что было бы лучше сказать, что все неполные определения структуры автоматически рассматриваются как форвардное объявление.

Изменить: Следуя комментарию о документации, давайте посмотрим на библейский язык C: Kerninghan & Ritchie - Язык программирования C, раздел "6.5. Самореференциальные структуры" говорит:

Иногда требуется вариация самореферентных структур: две структуры, которые относятся друг к другу. Способ справиться с этим:

struct t {
    ...
    struct s *p;   /* p points to an s */
};
struct s {
    ...
    struct t *q;   /* q points to a t */
};

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